保誠-保戶業務員媒合平台
ccd7179b7f9f0c685e1dc51187a82d203922e928..0632b21db7576e4a223eb4f6ca09b650616222f2
2024-01-17 Tomas
Fixed: 移除 lodash, @type/lodash 導致的 side-effect
0632b2 差異 | 目錄
2024-01-11 Tomas
project: remove lodash library
f316bd 差異 | 目錄
2024-01-11 Tomas
update: 改寫 lodash 方法為純 js
756f8a 差異 | 目錄
2023-12-26 Tomas
Refactor localStorage service to handle different data types
53eed9 差異 | 目錄
2023-12-25 Tomas
Update#178944: 移除 lodash 套件
f06576 差異 | 目錄
2023-12-25 Tomas
Fixed: prop interval type error
91c321 差異 | 目錄
2023-09-19 jack
Merge branch '滲透' of ssh://dev.pollex.com.tw:29418/pcalife/PAM into 滲透
509d17 差異 | 目錄
2023-09-18 jack
[UPDATE] 解決弱點Cleartext sensitive data in a database
aef49f 差異 | 目錄
2023-09-14 Tomas
Merge branch '滲透' of https://dev.pollex.com.tw:8443/r/pcalife/PAM into 滲透
e82822 差異 | 目錄
2023-09-14 Tomas
Update: http 攔截器,檢查 base url 的檢查 method
d33fc7 差異 | 目錄
2023-09-13 jack
[UPDATE] 移除sys out
8ba0f3 差異 | 目錄
2023-09-12 jack
[UPDATE] 解決弱點Unlogged security exception
2b11d3 差異 | 目錄
2023-09-12 jack
[UPDATE] 解決弱點Se: Incorrect definition of Serializable class [UPDATE] 解決弱點In...
74e563 差異 | 目錄
2023-09-08 jack
[UPDATE] 修改弱點掃描Dereference null return (stat)
a6afdf 差異 | 目錄
2023-09-08 jack
[UPDATE] 解決弱掃Dereference null return (stat)
a4a697 差異 | 目錄
2023-09-08 jack
[UPDATE] 解決弱掃Information exposure to log file, 將印在log的私密資料移除
f70b70 差異 | 目錄
2023-09-07 Tomas
Update: 0907-P6: Bad use of null-like value
8f60f9 差異 | 目錄
2023-09-05 Tomas
Update: 0901-P5 URL manipulation
0de81c 差異 | 目錄
2023-09-05 Tomas
Fixed: closeAppointment method Bad use of null-like value
2f6a81 差異 | 目錄
2023-09-05 Tomas
Fixed: PAM_0901_P6
dd0fdd 差異 | 目錄
2023-09-05 jack
Merge branch '滲透' of ssh://dev.pollex.com.tw:29418/pcalife/PAM into 滲透
3ecee0 差異 | 目錄
2023-09-05 jack
[UPDATE] 解決弱點掃描Use of hard-coded cryptographic key問題, 須把key參數移動到設定檔
b50be4 差異 | 目錄
2023-09-01 Tomas
Update: 處理弱掃問題: Trying to await on a null object
bce343 差異 | 目錄
2023-09-01 Tomas
Merge branch '滲透' of https://dev.pollex.com.tw:8443/r/pcalife/PAM into 滲透
23f937 差異 | 目錄
2023-09-01 Tomas
Update: P12.1 URL manipulation
1e8fb5 差異 | 目錄
2023-09-01 jack
[UPDATE] 解決弱點問題Dereference null return (stat)
22cfc1 差異 | 目錄
2023-09-01 jack
[UPDATE] 移除測試用程式碼
5f2709 差異 | 目錄
2023-09-01 jack
[UPDATE] 解決弱點Use of hard-coded cryptographic key , 把key移到設定檔
22a5ad 差異 | 目錄
2023-08-31 jack
[UPDATE] 解決弱點Insecure block cipher mode, AES CBC安全性上不足所以需要改為GCM模式來做加密
463c7f 差異 | 目錄
2023-08-29 jack
[UPDATE] 解決滲透TLS問題 [UPDATE] 解決CSRF問題
870dcd 差異 | 目錄
修改44個檔案
1057 ■■■■■ 已變更過的檔案
PAMapp/components/BackActionBar.vue 3 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/NavBar.vue 9 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/Ui/UiCarousel.vue 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/careerSelect.vue 7 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/editConsultantAvatar.vue 5 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/phoneContactTimePicker.vue 23 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/singleSelectBtn.vue 3 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/layouts/default.vue 5 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/layouts/home.vue 3 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/package-lock.json 372 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/package.json 7 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/accountSetting/index.vue 49 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/agentInfo/edit/_agentNo.vue 13 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/myAppointmentList.vue 6 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/questionnaire/_agentNo.vue 66 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/pages/quickFilter/index.vue 1 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/services/appointment.service.ts 15 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/services/httpClient.ts 30 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/services/login.service.ts 37 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/services/message-box.service.ts 3 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/services/my-consultant.service.ts 9 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/services/query-consultant.service.ts 14 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/shared/services/reviews.service.ts 5 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/store/localStorage.service.ts 18 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/tsconfig.json 1 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/config/ApplicationProperties.java 24 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/config/SecurityConfiguration.java 14 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/security/jwt/TokenProvider.java 7 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/security/provider/EServiceAuthenticationProvider.java 1 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/security/token/OtpAuthenticationToken.java 2 ●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/CustomerService.java 27 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/OtpUtilService.java 7 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/service/OtpWebService.java 12 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/web/rest/AccountResource.java 22 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/web/rest/EServiceResource.java 7 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/web/rest/TestLoginResource.java 86 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/java/com/pollex/pam/web/rest/UserResource.java 66 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/resources/config/application-dev.yml 2 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/resources/config/application-pollex.yml 8 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/resources/config/application-prod.yml 2 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/resources/config/application-sit.yml 2 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/resources/config/application-tls.yml 6 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/main/resources/config/application-uat.yml 2 ●●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
pamapi/src/test/java/com/pollex/pam/web/rest/AccountResourceIT.java 54 ●●●● 修補檔 | 檢視 | 原始 | 究查 | 歷程
PAMapp/components/BackActionBar.vue
@@ -11,7 +11,6 @@
import { namespace } from 'nuxt-property-decorator';
import { Vue, Component,} from 'vue-property-decorator';
import * as _ from 'lodash';
import { Role } from '~/shared/models/enum/Role';
const appointmentStore = namespace('appointment.store');
@@ -54,7 +53,7 @@
          break;
        case 'agentInfo':
          const agentFeatureLabel = this.$route.name.includes('edit') ? '編輯帳號資訊' : '查看帳號資訊';
          featureLabel = _.isEqual(this.currentRole,Role.ADMIN)
          featureLabel = this.currentRole === Role.ADMIN
                  ? agentFeatureLabel
                  : '業務員資訊'
          break;
PAMapp/components/NavBar.vue
@@ -54,7 +54,6 @@
  import { Vue, Component } from 'vue-property-decorator';
  import { Action, namespace, State, Watch } from 'nuxt-property-decorator';
  import { Role } from '~/shared/models/enum/Role';
  import * as _ from 'lodash';
  import { NotificationList } from '~/shared/models/reviews.model';
  import { AppointmentLog } from '~/shared/models/appointment.model';
  import loginService from '~/shared/services/login.service'
@@ -156,20 +155,20 @@
    routerNavigateTo(url: string): void {
      (this.$refs.dropdown as any).hide();
      _.isEqual(url,'')
      url === ''
        ? this.logout()
        : this.$router.push(_.isEqual(url,'/agentInfo/') ? url+this.consultantId :url);
        : this.$router.push(url === '/agentInfo/' ? url+this.consultantId : url);
    }
    pushRouterByLoginRole(): void {
      const link = _.isEqual(this.currentRole, Role.ADMIN) ? '/myAppointmentList/appointmentList' : '/';
      const link = this.currentRole === Role.ADMIN ? '/myAppointmentList/appointmentList' : '/';
      this.$router.push(link);
    }
    logout(): void {
      loginService.logout().then(() => {
        this.storageClear();
        _.isEqual(this.$route.name, 'index') ? location.reload() : this.$router.push('/');
        this.$route.name === 'index' ? location.reload() : this.$router.push('/');
      });
    }
PAMapp/components/Ui/UiCarousel.vue
@@ -4,7 +4,7 @@
        indicator-position="outside"
        arrow="never"
        trigger="click"
        interval="6000"
        :interval="6000"
        class="pam-home-carousel"
    >
PAMapp/components/careerSelect.vue
@@ -41,7 +41,6 @@
<script lang="ts">
  import { Component , PropSync , Vue} from "vue-property-decorator";
  import * as _ from 'lodash';
  @Component
  export default class CareerSelect extends Vue {
@@ -83,7 +82,7 @@
    patchInitValue(): void{
      if(this.syncCareerSelect){
        if(_.includes(['外勤','內勤'],this.syncCareerSelect)){
        if (['外勤','內勤'].includes(this.syncCareerSelect)) {
          this.career = this.syncCareerSelect;
        }else{
          this.career = '其他';
@@ -95,8 +94,8 @@
    patchCareer(): void {
      this.showJobDrawer = false;
      this.syncCareerSelect = this.career === '其他'
                          ? _.cloneDeep(this.career_Other)
                          : _.cloneDeep(this.career);
                          ? JSON.parse(JSON.stringify(this.career_Other))
                          : JSON.parse(JSON.stringify(this.career));
    }
    get isCareerValid(): boolean {
PAMapp/components/editConsultantAvatar.vue
@@ -38,7 +38,6 @@
  import { MessageBox } from 'element-ui';
  import { MessageBoxData } from 'element-ui/types/message-box';
  import _ from 'lodash';
  import myConsultantService from '~/shared/services/my-consultant.service';
@@ -74,7 +73,7 @@
    }
    handleAvatarUploaded(file:any): void {
      const isFollowUploadRule =_.includes(file.raw.type,'image/');
      const isFollowUploadRule = file.raw.type.includes('image/');
      isFollowUploadRule ? this.getImgSrc(file) : this.showFileUploadErrorMsg()
    }
@@ -96,7 +95,7 @@
    }
    private splitBase64WithCommon(base64: string): void {
      const splitBase64= _.split(base64, ','); // 為了把 data:image , base64 解析分開;
      const splitBase64 = base64.split(','); // 為了把 data:image , base64 解析分開;
      this.syncPhotoBase64 = splitBase64[1];
      // NOTE: 因為目前以 agentNO 取得 avatar 會失敗,
      // 故加上此判斷來防範不預期顯示'取消按鈕'的狀況。 [Tomas, 2022/1/3]
PAMapp/components/phoneContactTimePicker.vue
@@ -74,18 +74,17 @@
  import { dayTimeFrames } from "~/shared/const/day-time-frames";
  import { OptionBtnDto, OptionDto } from "~/shared/models/optionBtnDto.model";
  import { weekDays } from "~/shared/const/week-days";
  import * as _ from "lodash";
  @Component({
    filters:{
      titleFormatByIndex(index:number):string{
        const chineseNumber = ['一','二','三','四','五','六','七','八','九','十'];
        return '時段'+chineseNumber[index];
      },
      optionsFormat(selectedOptions:string[]|[], compareOptions:OptionDto): string{
        return _.isEqual(selectedOptions.length,compareOptions.options.length)
                ? compareOptions.selectAll
                : _.join(selectedOptions,',');
      },
      optionsFormat(selectedOptions = [], compareOptions) {
        return selectedOptions.length === compareOptions.options.length
          ? compareOptions.selectAll
          : selectedOptions.join(',');
      }
    }
  })
  export default class PhoneContactTimePicker extends Vue {
@@ -110,7 +109,7 @@
    //////////////////////////////////////////////////////////////////////
    get isOpenByDayPopUp(): boolean{
      return _.isEqual(this.popUpMode , TimePickerMode.SELECT_DAY)
      return this.popUpMode === TimePickerMode.SELECT_DAY;
    }
    set isOpenByDayPopUp(value:boolean){
@@ -118,7 +117,7 @@
    }
    get isOpenByTimePopUp(): boolean{
      return _.isEqual(this.popUpMode , TimePickerMode.SELECT_TIME);
      return this.popUpMode === TimePickerMode.SELECT_TIME;
    }
    set isOpenByTimePopUp(value:boolean){
@@ -128,7 +127,7 @@
    //////////////////////////////////////////////////////////////////////
    openPopUp(schedule:scheduleDto,index:number):void{
      this.initPickerControl = _.cloneDeep(schedule);
      this.initPickerControl = JSON.parse(JSON.stringify(schedule));
      this.popUpMode = TimePickerMode.SELECT_DAY;
      this.scheduleIndex = index;
    }
@@ -144,8 +143,8 @@
    }
    private initPickerFormatSort(initPickerControl:scheduleDto):scheduleDto{
      _.keys(initPickerControl).forEach(keyName=>{
        const options = _.isEqual(keyName,'selectWeekOptions') ? weekDays : dayTimeFrames;
      Object.keys(initPickerControl).forEach(keyName=>{
        const options = keyName === 'selectWeekOptions' ? weekDays : dayTimeFrames;
        initPickerControl[keyName] = this.getOptionsBySort(initPickerControl[keyName],options);
      })
      return initPickerControl;
@@ -153,7 +152,7 @@
    // 資料重新排序,回復一開始清單順序
    private getOptionsBySort( selectedOptions:string[] , options:OptionBtnDto[]): string[] {
      return options.map( o => _.includes(selectedOptions , o.label) ? o.label as string : '').filter(String);
      return options.map( o => selectedOptions.includes(o.label as string) ? o.label as string : '').filter(String);
    }
    addNewSchedule(): void {
PAMapp/components/singleSelectBtn.vue
@@ -21,7 +21,6 @@
<script lang="ts">
  import { Component, Prop, PropSync, Vue } from "nuxt-property-decorator";
  import { OptionBtnDto } from "~/shared/models/optionBtnDto.model";
  import * as _ from 'lodash';
  @Component
  export default class SingleSelectBtn extends Vue {
@@ -35,7 +34,7 @@
    //////////////////////////////////////////////////////////////////////
    patchValue(value: string | number): void {
      // 主要解決按鈕點擊兩次能回到,未點選的狀態
      this.syncSingleSelected = _.isEqual(this.syncSingleSelected, value) ? "" : value;
      this.syncSingleSelected = this.syncSingleSelected === value ? "" : value;
    }
  }
</script>
PAMapp/layouts/default.vue
@@ -25,7 +25,6 @@
<script lang="ts">
  import { Vue, Component } from 'vue-property-decorator';
  import * as _ from 'lodash';
  @Component
  export default class DefaultLayout extends Vue {
@@ -52,13 +51,13 @@
    // format to {page}-container tag
    private routeFormatContainClass(route: string): string {
      const needContainBgRoutes = ['recommendConsultant', 'questionnaire-agentNo'];
      return _.includes(needContainBgRoutes, route) ? route + '-container' : '';
      return needContainBgRoutes.includes(route) ? route + '-container' : '';
    };
    // format to {page}-banner or pam-no-banner tag
    private routeFormatBannerClass(route: string): string {
      const needBannerRoutes = ['recommendConsultant', 'quickFilter', 'myConsultantList-consultantList', 'myConsultantList-contactedList', 'myAppointmentList-appointmentList', 'myAppointmentList-contactedList', 'login', 'notification'];
      return _.includes(needBannerRoutes, route) ? route + '-banner' : 'pam-no-banner';
      return needBannerRoutes.includes(route) ? route + '-banner' : 'pam-no-banner';
    };
    private bannerText: FeatureBannerTitle= {
PAMapp/layouts/home.vue
@@ -9,7 +9,6 @@
</template>
<script lang="ts">
    import { Component ,Vue } from "nuxt-property-decorator";
    import * as _ from 'lodash';
    @Component
    export default class DefaultLayout extends Vue {
@@ -34,4 +33,4 @@
        }
    }
</style>
</style>
PAMapp/package-lock.json
@@ -3244,6 +3244,11 @@
        "@types/node": "*"
      }
    },
    "@types/crypto-js": {
      "version": "4.1.1",
      "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz",
      "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA=="
    },
    "@types/etag": {
      "version": "1.8.1",
      "resolved": "https://registry.npmjs.org/@types/etag/-/etag-1.8.1.tgz",
@@ -3360,12 +3365,6 @@
      "integrity": "sha512-1YXyYH83h6We1djyoUEqTlVyQtCfJAFXELSKW2ZRtjHD4hQ82CC4lvrv5D0l0FLcKBaiPbXyi3MpMsI9ZRgKsw==",
      "dev": true
    },
    "@types/lodash": {
      "version": "4.14.195",
      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz",
      "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==",
      "dev": true
    },
    "@types/mime": {
      "version": "1.3.2",
      "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
@@ -3376,6 +3375,15 @@
      "version": "16.18.38",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.38.tgz",
      "integrity": "sha512-6sfo1qTulpVbkxECP+AVrHV9OoJqhzCsfTNp5NIG+enM4HyM3HvZCO798WShIXBN0+QtDIcutJCjsVYnQP5rIQ=="
    },
    "@types/node-forge": {
      "version": "1.3.4",
      "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.4.tgz",
      "integrity": "sha512-08scBQriFsBbm/CuBWOXRMD1RG7ydFW01EDR6vGX27nxcj6E/jGSCOLdICNd8ETwQlLFXVBVA854RX6Y7vPSrQ==",
      "dev": true,
      "requires": {
        "@types/node": "*"
      }
    },
    "@types/optimize-css-assets-webpack-plugin": {
      "version": "5.0.5",
@@ -4119,6 +4127,11 @@
        "is-array-buffer": "^3.0.2",
        "is-shared-array-buffer": "^1.0.2"
      }
    },
    "asmcrypto.js": {
      "version": "2.3.2",
      "resolved": "https://registry.npmjs.org/asmcrypto.js/-/asmcrypto.js-2.3.2.tgz",
      "integrity": "sha512-3FgFARf7RupsZETQ1nHnhLUUvpcttcCq1iZCaVAbJZbCZ5VNRrNyvpDyHTOb0KC3llFcsyOT/a99NZcCbeiEsA=="
    },
    "asn1.js": {
      "version": "5.4.1",
@@ -5244,6 +5257,11 @@
      "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
      "dev": true
    },
    "code-point-at": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
      "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA=="
    },
    "collect-v8-coverage": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz",
@@ -5885,6 +5903,11 @@
      "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==",
      "dev": true
    },
    "deep-extend": {
      "version": "0.6.0",
      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
    },
    "deepmerge": {
      "version": "1.5.2",
      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz",
@@ -6492,6 +6515,11 @@
      "version": "0.1.2",
      "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
      "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ=="
    },
    "exit-hook": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
      "integrity": "sha512-MsG3prOVw1WtLXAZbM3KiYtooKR1LvxHh3VHsVtIy0uiUu8usxgB/94DP2HxtD/661lLdB6yzQ09lGJSQr6nkg=="
    },
    "expand-brackets": {
      "version": "2.1.4",
@@ -7332,7 +7360,6 @@
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
      "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==",
      "dev": true,
      "requires": {
        "ansi-regex": "^2.0.0"
      },
@@ -7340,8 +7367,7 @@
        "ansi-regex": {
          "version": "2.1.1",
          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
          "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
          "dev": true
          "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="
        }
      }
    },
@@ -7901,6 +7927,11 @@
      "requires": {
        "loose-envify": "^1.0.0"
      }
    },
    "invert-kv": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
      "integrity": "sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ=="
    },
    "ip": {
      "version": "1.1.8",
@@ -9720,12 +9751,25 @@
        "universalify": "^2.0.0"
      }
    },
    "keymirror": {
      "version": "0.1.1",
      "resolved": "https://registry.npmjs.org/keymirror/-/keymirror-0.1.1.tgz",
      "integrity": "sha512-vIkZAFWoDijgQT/Nvl2AHCMmnegN2ehgTPYuyy2hWQkQSntI0S7ESYqdLkoSe1HyEBFHHkCgSIvVdSEiWwKvCg=="
    },
    "kind-of": {
      "version": "3.2.2",
      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
      "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
      "requires": {
        "is-buffer": "^1.1.5"
      }
    },
    "klaw": {
      "version": "1.3.1",
      "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
      "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==",
      "requires": {
        "graceful-fs": "^4.1.9"
      }
    },
    "kleur": {
@@ -9763,6 +9807,14 @@
      "integrity": "sha512-K2yxgljj5TdCeRN1lBtO3/J26+AIDDDw+04y6VAiZbWcTdBwsYN6RrZBnW5DN/QiSIdKNjKdATLUUluWWFYTIA==",
      "requires": {
        "launch-editor": "^2.6.0"
      }
    },
    "lcid": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
      "integrity": "sha512-YiGkH6EnGrDGqLMITnGjXtGmNtjoXw9SVUzcaos8RBi7Ps0VBylkq+vOcY9QE5poLasPCR849ucFUkl0UzUyOw==",
      "requires": {
        "invert-kv": "^1.0.0"
      }
    },
    "leven": {
@@ -10274,6 +10326,237 @@
        "lodash": "^4.17.15"
      }
    },
    "node-cipher": {
      "version": "6.3.3",
      "resolved": "https://registry.npmjs.org/node-cipher/-/node-cipher-6.3.3.tgz",
      "integrity": "sha512-GL1yYULPOPDpu+4Pwb7wGgnmq0MjZEkGrxtqI1fmSo2Dke84shY2e+P4F6J+JU1ehjYFt40AC43AnW4y6RZi+g==",
      "requires": {
        "chalk": "^1.1.1",
        "commander": "^2.9.0",
        "debug": "^2.2.0",
        "fs-extra": "^0.26.4",
        "inquirer": "^0.11.2",
        "keymirror": "^0.1.1",
        "lodash": "^4.0.0",
        "rc": "^1.1.6",
        "yargs": "^3.32.0"
      },
      "dependencies": {
        "ansi-escapes": {
          "version": "1.4.0",
          "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz",
          "integrity": "sha512-wiXutNjDUlNEDWHcYH3jtZUhd3c4/VojassD8zHdHCY13xbZy2XbW+NKQwA0tWGBVzDA9qEzYwfoSsWmviidhw=="
        },
        "ansi-regex": {
          "version": "2.1.1",
          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
          "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA=="
        },
        "ansi-styles": {
          "version": "2.2.1",
          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
          "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA=="
        },
        "camelcase": {
          "version": "2.1.1",
          "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
          "integrity": "sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw=="
        },
        "chalk": {
          "version": "1.1.3",
          "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
          "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
          "requires": {
            "ansi-styles": "^2.2.1",
            "escape-string-regexp": "^1.0.2",
            "has-ansi": "^2.0.0",
            "strip-ansi": "^3.0.0",
            "supports-color": "^2.0.0"
          }
        },
        "cli-cursor": {
          "version": "1.0.2",
          "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
          "integrity": "sha512-25tABq090YNKkF6JH7lcwO0zFJTRke4Jcq9iX2nr/Sz0Cjjv4gckmwlW6Ty/aoyFd6z3ysR2hMGC2GFugmBo6A==",
          "requires": {
            "restore-cursor": "^1.0.1"
          }
        },
        "cli-width": {
          "version": "1.1.1",
          "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.1.1.tgz",
          "integrity": "sha512-eMU2akIeEIkCxGXUNmDnJq1KzOIiPnJ+rKqRe6hcxE3vIOPvpMrBYOn/Bl7zNlYJj/zQxXquAnozHUCf9Whnsg=="
        },
        "cliui": {
          "version": "3.2.0",
          "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
          "integrity": "sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==",
          "requires": {
            "string-width": "^1.0.1",
            "strip-ansi": "^3.0.1",
            "wrap-ansi": "^2.0.0"
          }
        },
        "debug": {
          "version": "2.6.9",
          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
          "requires": {
            "ms": "2.0.0"
          }
        },
        "figures": {
          "version": "1.7.0",
          "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
          "integrity": "sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ==",
          "requires": {
            "escape-string-regexp": "^1.0.5",
            "object-assign": "^4.1.0"
          }
        },
        "fs-extra": {
          "version": "0.26.7",
          "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz",
          "integrity": "sha512-waKu+1KumRhYv8D8gMRCKJGAMI9pRnPuEb1mvgYD0f7wBscg+h6bW4FDTmEZhB9VKxvoTtxW+Y7bnIlB7zja6Q==",
          "requires": {
            "graceful-fs": "^4.1.2",
            "jsonfile": "^2.1.0",
            "klaw": "^1.0.0",
            "path-is-absolute": "^1.0.0",
            "rimraf": "^2.2.8"
          }
        },
        "inquirer": {
          "version": "0.11.4",
          "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.11.4.tgz",
          "integrity": "sha512-QR+2TW90jnKk9LUUtbcA3yQXKt2rDEKMh6+BAZQIeumtzHexnwVLdPakSslGijXYLJCzFv7GMXbFCn0pA00EUw==",
          "requires": {
            "ansi-escapes": "^1.1.0",
            "ansi-regex": "^2.0.0",
            "chalk": "^1.0.0",
            "cli-cursor": "^1.0.1",
            "cli-width": "^1.0.1",
            "figures": "^1.3.5",
            "lodash": "^3.3.1",
            "readline2": "^1.0.1",
            "run-async": "^0.1.0",
            "rx-lite": "^3.1.2",
            "string-width": "^1.0.1",
            "strip-ansi": "^3.0.0",
            "through": "^2.3.6"
          },
          "dependencies": {
            "lodash": {
              "version": "3.10.1",
              "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
              "integrity": "sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ=="
            }
          }
        },
        "is-fullwidth-code-point": {
          "version": "1.0.0",
          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
          "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==",
          "requires": {
            "number-is-nan": "^1.0.0"
          }
        },
        "jsonfile": {
          "version": "2.4.0",
          "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
          "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==",
          "requires": {
            "graceful-fs": "^4.1.6"
          }
        },
        "ms": {
          "version": "2.0.0",
          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
          "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
        },
        "onetime": {
          "version": "1.1.0",
          "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
          "integrity": "sha512-GZ+g4jayMqzCRMgB2sol7GiCLjKfS1PINkjmx8spcKce1LiVqcbQreXwqs2YAFXC6R03VIG28ZS31t8M866v6A=="
        },
        "restore-cursor": {
          "version": "1.0.1",
          "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
          "integrity": "sha512-reSjH4HuiFlxlaBaFCiS6O76ZGG2ygKoSlCsipKdaZuKSPx/+bt9mULkn4l0asVzbEfQQmXRg6Wp6gv6m0wElw==",
          "requires": {
            "exit-hook": "^1.0.0",
            "onetime": "^1.0.0"
          }
        },
        "rimraf": {
          "version": "2.7.1",
          "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
          "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
          "requires": {
            "glob": "^7.1.3"
          }
        },
        "run-async": {
          "version": "0.1.0",
          "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz",
          "integrity": "sha512-qOX+w+IxFgpUpJfkv2oGN0+ExPs68F4sZHfaRRx4dDexAQkG83atugKVEylyT5ARees3HBbfmuvnjbrd8j9Wjw==",
          "requires": {
            "once": "^1.3.0"
          }
        },
        "string-width": {
          "version": "1.0.2",
          "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
          "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==",
          "requires": {
            "code-point-at": "^1.0.0",
            "is-fullwidth-code-point": "^1.0.0",
            "strip-ansi": "^3.0.0"
          }
        },
        "strip-ansi": {
          "version": "3.0.1",
          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
          "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
          "requires": {
            "ansi-regex": "^2.0.0"
          }
        },
        "supports-color": {
          "version": "2.0.0",
          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
          "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g=="
        },
        "wrap-ansi": {
          "version": "2.1.0",
          "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
          "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==",
          "requires": {
            "string-width": "^1.0.1",
            "strip-ansi": "^3.0.1"
          }
        },
        "y18n": {
          "version": "3.2.2",
          "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz",
          "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ=="
        },
        "yargs": {
          "version": "3.32.0",
          "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz",
          "integrity": "sha512-ONJZiimStfZzhKamYvR/xvmgW3uEkAUFSP91y2caTEPhzF6uP2JfPiVZcq66b/YR0C3uitxSV7+T1x8p5bkmMg==",
          "requires": {
            "camelcase": "^2.0.1",
            "cliui": "^3.0.3",
            "decamelize": "^1.1.1",
            "os-locale": "^1.4.0",
            "string-width": "^1.0.1",
            "window-size": "^0.1.4",
            "y18n": "^3.2.0"
          }
        }
      }
    },
    "node-fetch": {
      "version": "2.6.12",
      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
@@ -10307,6 +10590,11 @@
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.2.0.tgz",
      "integrity": "sha512-5IAMBTl9p6PaAjYCnMv5FmqIF6GcZnawAVnzaCG0rX2aYZJ4CxEkZNtVPuTRug7fL7wyM5BQYTlAzcyMPi6oTQ=="
    },
    "node-forge": {
      "version": "1.3.1",
      "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
      "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA=="
    },
    "node-html-parser": {
      "version": "6.1.5",
@@ -10447,6 +10735,11 @@
      "requires": {
        "boolbase": "^1.0.0"
      }
    },
    "number-is-nan": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
      "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ=="
    },
    "nuxt": {
      "version": "2.17.1",
@@ -10981,6 +11274,14 @@
      "version": "0.3.0",
      "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
      "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A=="
    },
    "os-locale": {
      "version": "1.4.0",
      "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
      "integrity": "sha512-PRT7ZORmwu2MEFt4/fv3Q+mEfN4zetKxufQrkShY2oGvUms9r8otu5HfdyIFHkYXjO7laNsoVGmM2MANfuTA8g==",
      "requires": {
        "lcid": "^1.0.0"
      }
    },
    "os-tmpdir": {
      "version": "1.0.2",
@@ -12081,6 +12382,24 @@
      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="
    },
    "rc": {
      "version": "1.2.8",
      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
      "requires": {
        "deep-extend": "^0.6.0",
        "ini": "~1.3.0",
        "minimist": "^1.2.0",
        "strip-json-comments": "~2.0.1"
      },
      "dependencies": {
        "strip-json-comments": {
          "version": "2.0.1",
          "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
          "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="
        }
      }
    },
    "rc9": {
      "version": "2.1.1",
      "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.1.tgz",
@@ -12146,6 +12465,31 @@
      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
      "requires": {
        "picomatch": "^2.2.1"
      }
    },
    "readline2": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz",
      "integrity": "sha512-8/td4MmwUB6PkZUbV25uKz7dfrmjYWxsW8DVfibWdlHRk/l/DfHKn4pU+dfcoGLFgWOdyGCzINRQD7jn+Bv+/g==",
      "requires": {
        "code-point-at": "^1.0.0",
        "is-fullwidth-code-point": "^1.0.0",
        "mute-stream": "0.0.5"
      },
      "dependencies": {
        "is-fullwidth-code-point": {
          "version": "1.0.0",
          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
          "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==",
          "requires": {
            "number-is-nan": "^1.0.0"
          }
        },
        "mute-stream": {
          "version": "0.0.5",
          "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz",
          "integrity": "sha512-EbrziT4s8cWPmzr47eYVW3wimS4HsvlnV5ri1xw1aR6JQo/OrJX5rkl32K/QQHdxeabJETtfeaROGhd8W7uBgg=="
        }
      }
    },
    "regenerate": {
@@ -12455,6 +12799,11 @@
      "requires": {
        "aproba": "^1.1.1"
      }
    },
    "rx-lite": {
      "version": "3.1.2",
      "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz",
      "integrity": "sha512-1I1+G2gteLB8Tkt8YI1sJvSIfa0lWuRtC8GjvtyPBcLSF5jBCCJJqKrpER5JU5r6Bhe+i9/pK3VMuUcXu0kdwQ=="
    },
    "rxjs": {
      "version": "6.6.7",
@@ -15341,6 +15690,11 @@
        "string-width": "^4.0.0"
      }
    },
    "window-size": {
      "version": "0.1.4",
      "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz",
      "integrity": "sha512-2thx4pB0cV3h+Bw7QmMXcEbdmOzv9t0HFplJH/Lz6yu60hXYy5RT8rUu+wlIreVxWsGN20mo+MHeCSfUpQBwPw=="
    },
    "worker-farm": {
      "version": "1.7.0",
      "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz",
PAMapp/package.json
@@ -18,11 +18,14 @@
  "dependencies": {
    "@nuxtjs/axios": "^5.13.6",
    "@nuxtjs/style-resources": "^1.2.1",
    "@types/crypto-js": "^4.1.1",
    "@types/swiper": "^5.4.3",
    "asmcrypto.js": "^2.3.2",
    "core-js": "^3.18.3",
    "crypto-js": "^4.1.1",
    "element-ui": "^2.15.6",
    "lodash": "^4.17.21",
    "node-cipher": "^6.3.3",
    "node-forge": "^1.3.1",
    "nuxt": "^2.15.8",
    "nuxt-property-decorator": "^2.9.1",
    "swiper": "^5.4.5",
@@ -35,7 +38,7 @@
    "@nuxt/types": "^2.15.8",
    "@nuxt/typescript-build": "^2.1.0",
    "@nuxtjs/dotenv": "^1.4.1",
    "@types/lodash": "^4.14.176",
    "@types/node-forge": "^1.3.4",
    "@vue/test-utils": "^1.2.2",
    "babel-core": "7.0.0-bridge.0",
    "babel-jest": "^27.3.0",
PAMapp/pages/accountSetting/index.vue
@@ -164,8 +164,6 @@
import { Vue,Component } from 'vue-property-decorator'
import { namespace } from 'vuex-class';
import _ from 'lodash';
import accountSettingService from '~/shared/services/account-setting.service';
import { UserSetting } from '~/shared/models/account.model';
@@ -202,28 +200,29 @@
////////////////////////////////////////////////////////////
  mounted(){
            accountSettingService.getUserAccountSetting().then((userInfo: UserSetting)=>{
                this.defaultUserSetting = _.cloneDeep({
                    name : userInfo.name || '',
                    phone: userInfo.phone || '',
                    email: userInfo.email || '',
                });
                this.phoneValue    = this.defaultUserSetting.phone!;
                this.userNameValue = this.defaultUserSetting.name!;
                this.emailValue    = this.defaultUserSetting.email!;
            })
        }
    accountSettingService.getUserAccountSetting().then((userInfo: UserSetting)=>{
      this.defaultUserSetting = {
        name : userInfo.name || '',
        phone: userInfo.phone || '',
        email: userInfo.email || '',
      };
      this.phoneValue    = this.defaultUserSetting.phone!;
      this.userNameValue = this.defaultUserSetting.name!;
      this.emailValue    = this.defaultUserSetting.email!;
    })
  }
////////////////////////////////////////////////////////////
  editField(fieldName: string): void {
            const enablePromise = new Promise((resolve, reject) => { // 此為promise語法
                resolve((this as any)[`${fieldName}Disabled`] = false);
            });
            const targetInput = this.$refs[fieldName] as any;
            enablePromise.then((_) => {
                targetInput.focus();
            });
        }
      const enablePromise = new Promise((resolve, reject) => { // 此為promise語法
          resolve((this as any)[`${fieldName}Disabled`] = false);
      });
      const targetInput = this.$refs[fieldName] as any;
      enablePromise.then((_) => {
          targetInput.focus();
      });
  }
  updateAccountSetting(): void {
      const editSettingInfo: UserSetting = {
          name: this.userNameValue,
@@ -238,10 +237,10 @@
  }
  private resetSettingForm(): void {
            this.userNameDisabled = true;
            this.userPhoneDisabled = true;
            this.userEmailDisabled = true ;
        }
      this.userNameDisabled = true;
      this.userPhoneDisabled = true;
      this.userEmailDisabled = true ;
  }
  editOtherContactType(){
    this.otherContactType = true;
PAMapp/pages/agentInfo/edit/_agentNo.vue
@@ -264,7 +264,6 @@
import { Context } from '@nuxt/types';
import { namespace } from 'nuxt-property-decorator';
import { Vue, Component, Prop } from 'vue-property-decorator';
import * as _ from "lodash";
import myConsultantService from '~/shared/services/my-consultant.service';
import accountSettingService from '~/shared/services/account-setting.service';
@@ -410,7 +409,7 @@
    this.editInfoValue = {
      ...this.defaultAgentInfoSetting,
      expertise: _.cloneDeep(this.defaultAgentInfoSetting.expertise),
      expertise: JSON.parse(JSON.stringify(this.defaultAgentInfoSetting.expertise)),
      communicationStyle: this.defaultAgentInfoSetting.communicationStyle?.split('、') || [],
    };
  }
@@ -467,11 +466,11 @@
  }
  get phoneValid(): boolean {
            const rule = /^09[0-9]{8}$/;
            return this.editInfoValue.phoneNumber
            ? rule.test(this.editInfoValue.phoneNumber) && _.isEqual(this.editInfoValue.phoneNumber.length,10)
            : true;
        }
    const rule = /^09[0-9]{8}$/;
    return this.editInfoValue.phoneNumber
      ? rule.test(this.editInfoValue.phoneNumber) && this.editInfoValue.phoneNumber.length === 10
      : true;
  }
  get emailValid() {
      const rule = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/;
PAMapp/pages/myAppointmentList.vue
@@ -64,8 +64,6 @@
<script lang="ts">
import { Vue, Component, Watch, namespace } from 'nuxt-property-decorator';
import * as _ from 'lodash';
import appointmentService from '~/shared/services/appointment.service';
import { Appointment } from '~/shared/models/appointment.model';
import { ContactStatus } from '~/shared/models/enum/contact-status';
@@ -183,8 +181,8 @@
    // format to {page}-banner or pam-no-banner tag
    private routeFormatBannerClass(route: string): string {
        const needBannerTags = ['myAppointmentList-appointmentList', 'myAppointmentList-closedList'];
        return _.includes(needBannerTags, route) ? route + '-banner' : 'pam-no-banner';
      const needBannerTags = ['myAppointmentList-appointmentList', 'myAppointmentList-closedList'];
      return needBannerTags.includes(route) ? route + '-banner' : 'pam-no-banner';
    };
}
</script>
PAMapp/pages/questionnaire/_agentNo.vue
@@ -160,7 +160,6 @@
<script lang="ts">
import { Vue, Component, State, Action, Watch, namespace } from 'nuxt-property-decorator';
import { getRequestsFromStorage, removeRequestQuestionFromStorage, setRequestsToStorage } from '~/shared/storageRequests';
import _ from 'lodash';
import accountSettingService from '~/shared/services/account-setting.service';
import appointmentService from '~/shared/services/appointment.service';
@@ -277,37 +276,37 @@
    ];
    quesAboutList = [
                  {
                      title:'健康與保障',
                      content:'唯有把身體照顧好,才是保障幸福之本,不做盲目燃燒的蠟燭,只做綻開的陽光,陪孩子多走一哩路,人生的美正要開展。'
                  },
                  {
                      title:'子女教育',
                      content:'孩子,我們是雙方的導師也是學生,面對未來要並肩作戰,學會勇敢無畏、克服挫折、善於理財,這條路上我們一起學。'
                  },
                  {
                      title:'資產規劃',
                      content:'真正的財富來自嚴謹規劃資產傳承,為人生蓋一堵抵禦財務風險的牆,確保資產穩健成長,替全家族的未來做好萬全準備。'
                  },
                  {
                      title:'樂活退休',
                      content:'拼一輩子,退休後的日子要輕鬆快活,就得提早透過保險商品規劃退休財務,替自己創造穩定收入,為精彩的熟年人生揭開序幕。'
                  },
                  {
                      title:'保單健檢/規劃',
                      content:'全面檢視自己的保障結構是否符合現在或未來的風險移轉需求。'
                  },
                  {
                      title:'分紅保單',
                      content:'分紅保單是兼具「分攤風險」與「紅利共享」特色的保單,具有一定穩定度,讓您可以同時享有壽險保障及紅利!'
                  }
      {
          title:'健康與保障',
          content:'唯有把身體照顧好,才是保障幸福之本,不做盲目燃燒的蠟燭,只做綻開的陽光,陪孩子多走一哩路,人生的美正要開展。'
      },
      {
          title:'子女教育',
          content:'孩子,我們是雙方的導師也是學生,面對未來要並肩作戰,學會勇敢無畏、克服挫折、善於理財,這條路上我們一起學。'
      },
      {
          title:'資產規劃',
          content:'真正的財富來自嚴謹規劃資產傳承,為人生蓋一堵抵禦財務風險的牆,確保資產穩健成長,替全家族的未來做好萬全準備。'
      },
      {
          title:'樂活退休',
          content:'拼一輩子,退休後的日子要輕鬆快活,就得提早透過保險商品規劃退休財務,替自己創造穩定收入,為精彩的熟年人生揭開序幕。'
      },
      {
          title:'保單健檢/規劃',
          content:'全面檢視自己的保障結構是否符合現在或未來的風險移轉需求。'
      },
      {
          title:'分紅保單',
          content:'分紅保單是兼具「分攤風險」與「紅利共享」特色的保單,具有一定穩定度,讓您可以同時享有壽險保障及紅利!'
      }
    ];
    myRequest: AppointmentRequests = {
      name           : '',
      phone          : '',
      email          : '',
      contactType    : _.isEqual(this.userInfo?.contactType,ContactType.SMS) ? ContactType.PHONE: ContactType.EMAIL,
      contactType    : this.userInfo?.contactType === ContactType.SMS ? ContactType.PHONE: ContactType.EMAIL,
      gender         : '',
      age            : '',
      job            : '',
@@ -452,6 +451,7 @@
    ////////////////////////////////////////////////////////////////////////////
    async sentDemand() {
      if (this.isEditBtn) {
        await this.editAppointmentDemand();
@@ -478,10 +478,11 @@
      this.storageUserInfo(this.userInfo);
    }
    private editAppointmentDemand() {
      const info = {
          ...this.myRequest,
          requirement: _.map(this.myRequest.requirement,o=>o).toString(),
          requirement: this.myRequest.requirement.map(o => o).toString(),
          hopeContactTime: this.myRequest.phone && this.phoneValid ? this.getHopeContactTime() :'',
          id: this.appointmentId,
          otherRequirement: null
@@ -496,7 +497,7 @@
    private sentAppointmentDemand() {
        const data: AppointmentParams = {
          ...this.myRequest,
          requirement: _.map(this.myRequest.requirement,o=>o).toString(),
          requirement: this.myRequest.requirement.map(o => o).toString(),
          hopeContactTime: this.myRequest.phone && this.phoneValid ? this.getHopeContactTime() :'',
          agentNo: this.$route.params.agentNo
        };
@@ -537,7 +538,7 @@
    get phoneValid(): boolean {
      const rule = /^09[0-9]{8}$/;
      return this.myRequest.phone
            ? rule.test(this.myRequest.phone) && _.isEqual(this.myRequest.phone.length,10)
            ? rule.test(this.myRequest.phone) && this.myRequest.phone.length === 10
            : true;
    }
@@ -552,9 +553,9 @@
    }
    get isDisabledSubmitBtn(): boolean {
           return _.includes(this.myRequest.contactType,ContactType.PHONE)
      ? !this.isHopeContactTimeDone() || !this.emailValid
      : !this.phoneValid;
      return this.myRequest.contactType.includes(ContactType.PHONE)
        ? !this.isHopeContactTimeDone() || !this.emailValid
        : !this.phoneValid;
    }
    private isHopeContactTimeDone():boolean{
@@ -739,4 +740,3 @@
</style>
PAMapp/pages/quickFilter/index.vue
@@ -71,7 +71,6 @@
  import { Seniority } from '~/shared/models/enum/seniority';
  import { Vue, Component, namespace } from 'nuxt-property-decorator';
  import queryConsultantService from '~/shared/services/query-consultant.service';
  import _ from 'lodash';
  const localStorage = namespace('localStorage');
  const seniorityMap={
PAMapp/shared/services/appointment.service.ts
@@ -57,10 +57,11 @@
  async createMemo(memoInfo: createdMemoInfo): Promise<AppointmentMemoInfo> {
    try {
      const response = await http.post('/appointment/memo/create', memoInfo);
      if (response !== null) {
        return response.data;
      } else {
      // 弱掃 test2: 改為判斷 !response
      if (!response) {
        throw new Error('http.post returned null-like value.');
      } else {
        return response.data;
      }
    } catch (error) {
      // 可以在此處處理錯誤或回傳預設值
@@ -104,13 +105,13 @@
  async closeAppointment(appointmentInfo: ToDoneAppointment | ToCloseAppointment) {
    try {
      const response = await http.post(`/appointment/close`, appointmentInfo);
      if (response !== null) {
        return response.data;
      if (!response) {
        throw new Error('http.post returned a null-like value.');
      } else {
        throw new Error('http.post returned null-like value.');
        return response.data;
      }
    } catch (error) {
      // 可以在此處處理錯誤或回傳預設值
      // 在這裡處理錯誤,例如回傳預設值或記錄錯誤訊息
      console.error('An error occurred while closing appointment:', error);
      throw error;
    }
PAMapp/shared/services/httpClient.ts
@@ -1,6 +1,4 @@
import { AxiosRequestConfig, AxiosError, AxiosResponse} from 'axios';
import axios from 'axios';
import _ from 'lodash';
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
import messageBoxService from './message-box.service';
@@ -13,11 +11,21 @@
  '/api/access_analysis/insert'
];
const BASE_URL = process.env.BASE_URL;
function getBaseUrl(): string {
  const baseUrl = process.env.BASE_URL;
  if (!baseUrl) {
    throw new Error('BASE_URL is not defined in process.env');
  }
  // const pattern = /^(https?:\/\/)[\w.-]+(:\d+)?/i; // 更嚴格的URL驗證
  // if (!pattern.test(baseUrl)) {
  //   throw new Error('Invalid BASE_URL');
  // }
  // 不需要再清理URL,因為它已經是絕對URL
  return baseUrl;
}
export const http = axios.create({
  baseURL: BASE_URL,
  withCredentials: true,
  baseURL: getBaseUrl(), // 使用函數動態獲取baseURL
  withCredentials: true
});
let apiNumber = 0;
@@ -76,11 +84,15 @@
      messageBoxService.showErrorMessage('', error);
      return
    }
    if (!_.includes(notRequireInterceptorErrorUrl, error.config.url)) {
    if (!notRequireInterceptorErrorUrl.includes(error.config.url)) {
      switch (error.response.status) {
        case 401:
          Promise.all([messageBoxService.showErrorMessage('登入逾時'), window.$nuxt.$store.dispatch('localStorage/actionStorageClear')]).then(() => {
            _.isEqual(window.$nuxt.$route.name, 'index') ? location.reload() : window.$nuxt.$router.push('/');
            if (window.$nuxt.$route.name === 'index') {
              location.reload();
            } else {
              window.$nuxt.$router.push('/');
            }
          });
          break;
PAMapp/shared/services/login.service.ts
@@ -1,7 +1,7 @@
import { http } from "./httpClient";
import { AxiosResponse } from 'axios';
import _ from "lodash";
import CryptoJS from "crypto-js";
import forge from "node-forge";
import { http } from "./httpClient";
// import CryptoJS from "asmcrypto-js";
import { ConsultantLoginInfo } from "../models/ConsultantLoginInfo";
import { LoginRequest } from "../models/loginRequest.model";
@@ -15,7 +15,8 @@
    async sendOtp(loginInfo: LoginRequest, verifyCode: string): Promise<OtpInfo> {
      try {
        const response = await http.post(`/otp/sendOtp/${verifyCode}`, loginInfo);
        if (response !== null) {
        // 弱掃Test1: 改為 if (response)
        if (response) {
          return response.data;
        } else {
          throw new Error('http.post returned null-like value.');
@@ -70,8 +71,8 @@
    return http.get('/login/validate/get_img_code',{ responseType : 'arraybuffer' })
      .then( response => {
        const toBase64 = window.btoa(
                          _.reduce( new Uint8Array(response.data),(data,byte) =>
                            data + String.fromCharCode(byte),'')
                          Array.from(new Uint8Array(response.data)).reduce((data, byte) =>
                            data + String.fromCharCode(byte), '')
                        );
        const imgSrc = `data:image/jpeg;base64,${toBase64}`;
        return imgSrc;
@@ -86,19 +87,19 @@
  /** 顧問登入 **/
  logInToConsultant(consultantDto:ConsultantLoginInfo, verificationCode: string):Promise<AxiosResponse<LoginSuccessToken>>{
      const key = "PAMKEY1234567890";
      const iv = "0123456789abcdef";
  const iv = "0123456789abcdef";
  const key = "PAMKEY1234567890";
  const cipher = forge.cipher.createCipher('AES-GCM', key);
  cipher.start({
    iv:iv
  });
  cipher.update(forge.util.createBuffer(forge.util.encodeUtf8(consultantDto.password)));
  cipher.finish();
  const encry = cipher.output;
  var tag = cipher.mode.tag;
  const encryptedPassword = window.btoa(encry.data+tag.data);
      const keyBytes = CryptoJS.enc.Utf8.parse(key);
      const ivBytes = CryptoJS.enc.Utf8.parse(iv);
      const encrypted = CryptoJS.AES.encrypt(consultantDto.password, keyBytes, {
        iv: ivBytes,
        mode: CryptoJS.mode.CBC,
        padding: CryptoJS.pad.Pkcs7,
      });
    return http.post(`/eService/authenticate/${verificationCode}`, { ...consultantDto, password: encrypted.toString() });
    return http.post(`/eService/authenticate/${verificationCode}`, { ...consultantDto, password: encryptedPassword});
  }
  async logout(): Promise<void> {
PAMapp/shared/services/message-box.service.ts
@@ -1,6 +1,5 @@
import { MessageBox } from 'element-ui';
import { MessageBoxData } from "element-ui/types/message-box";
import _ from "lodash";
class MessageBoxService {
@@ -22,7 +21,7 @@
  // 暫時用逗號去斷行
  private breakTextByComma(errorMsg:string):string{
    return _.split(errorMsg,",").join('<br>');
    return errorMsg.split(",").join('<br>');
  }
}
PAMapp/shared/services/my-consultant.service.ts
@@ -1,9 +1,8 @@
import _ from "lodash";
import { http } from "./httpClient";
import { AgentInfo } from '~/shared/models/agent-info.model';
import { Consultant } from "../models/consultant.model";
import { Appointment } from "../models/appointment.model";
import { Consultant } from "../models/consultant.model";
import { http } from "./httpClient";
class MyConsultantService {
  async getFavoriteConsultantList(): Promise<Consultant[]> {
@@ -56,8 +55,8 @@
    return http.get(`/consultant/avatar/${agentNo}`,{ responseType : 'arraybuffer' })
      .then( response => {
        const toBase64 = window.btoa(
                          _.reduce( new Uint8Array(response.data),(data,byte) =>
                            data + String.fromCharCode(byte),'')
                          Array.from(new Uint8Array(response.data)).reduce((data, byte) =>
                            data + String.fromCharCode(byte), '')
                        );
        const imgSrc = `data:image/png;base64,${toBase64}`;
        return imgSrc;
PAMapp/shared/services/query-consultant.service.ts
@@ -61,12 +61,14 @@
   */
  async  appointmentDemand(data: AppointmentParams) {
    try {
      const response = await http.post('/appointment/customer/create', data);
      if (response !== null) {
        return response.data;
      } else {
        throw new Error('http.post returned null-like value.');
      }
      // 弱掃Test4: 改為 promise.then 寫法
      return http.post('/appointment/customer/create', data).then((res) => {
        if (res) {
          return res['data'];
        } else {
          throw new Error('http.post returned null-like value.');
        }
      })
    } catch (error) {
      // 可以在此處處理錯誤或回傳預設值
      console.error('An error occurred while creating appointment demand:', error);
PAMapp/shared/services/reviews.service.ts
@@ -29,8 +29,9 @@
  async  sendSatisfactionToClient(appointmentId: number): Promise<any> {
    try {
      const response = await http.post(`/consultant/sendSatisfactionToClient/${appointmentId}`);
      if (response !== null) {
        return response.data;
      // 弱掃TEST3: 判斷 response && response.data
      if (response) {
        return response && response.data;
      } else {
        throw new Error('http.post returned null-like value.');
      }
PAMapp/store/localStorage.service.ts
@@ -1,5 +1,4 @@
import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators';
import _ from 'lodash';
import { Action, Module, Mutation, VuexModule } from 'vuex-module-decorators';
export enum LocalStorageItem {
@@ -32,9 +31,9 @@
   //** 初始物件狀態 **//
  private getInitLocalStorageDto(): storageDataDto {
    const initLocalStorageDto = {} as storageDataDto;
    _.forEach( LocalStorageItem , keyName =>
      initLocalStorageDto[keyName] = this.processOfStorageGetItem(keyName)
    )
    Object.values(LocalStorageItem).forEach(keyName => {
      initLocalStorageDto[keyName] = this.processOfStorageGetItem(keyName);
    });
    return initLocalStorageDto;
  }
  //** 取得 localStorage資料 **/
@@ -42,13 +41,12 @@
    const storageData = localStorage.getItem(keyName);
    const itemType = initialValueOfStorageItemDto[keyName];
    const prepareData = storageData
                          ? _.isEqual(itemType,String)
                          ? itemType === String
                            ? storageData
                            : JSON.parse(storageData)
                          : null;
    return prepareData
  }
  ///////////////////////////////////////////////// Mutation
  /** 儲存進 localStorageDto **/
@@ -64,7 +62,7 @@
  //** 移除全部物件 **//
  @Mutation clearStoreData(): void {
      _.keys(this.localStorageDto)
      Object.keys(this.localStorageDto)
      .forEach(keyName => {
        localStorage.removeItem(keyName);
        this.localStorageDto[keyName]=null;
@@ -75,7 +73,7 @@
  //** 物件儲存 **//
  @Action storeData( data: storageDataDto ): void {
    _.keys(data).forEach(keyName=>{
    Object.keys(data).forEach(keyName=>{
      const newObj = {
        keyName:keyName,
        value:data[keyName]
@@ -87,7 +85,7 @@
  //** 將需要 存進 localStorage 格式 **//
  @Action localStorageSet( setItem:SetItemDto ): void {
    const itemType = initialValueOfStorageItemDto[setItem.keyName];
    const prepareData = _.isEqual(itemType,String)
    const prepareData = typeof itemType === 'string'
                          ? setItem.value
                          : JSON.stringify(setItem.value);
    localStorage.setItem(setItem.keyName,prepareData);
PAMapp/tsconfig.json
@@ -28,7 +28,6 @@
      "@nuxt/types",
      "@types/node",
      "@nuxtjs/axios",
      "@types/lodash",
    ]
  },
  "exclude": [
pamapi/src/main/java/com/pollex/pam/config/ApplicationProperties.java
@@ -26,6 +26,8 @@
    private SendSMSProperties sms;
    private SendEmailProperties email;
    private String fileFolderPath;
    private String aesKey;
    private String defaultPaxxword;
    public boolean isMockLogin() {
        return mockLogin;
@@ -114,5 +116,27 @@
    public void setFileFolderPath(String fileFolderPath) {
        this.fileFolderPath = fileFolderPath;
    }
    @Override
    public String getAesKey() {
        return aesKey;
    }
    public void setAesKey(String aesKey) {
        this.aesKey = aesKey;
    }
    @Override
    public String getDefaultPaxxword() {
        return defaultPaxxword;
    }
    public void setDefaultPaxxword(String defaultPaxxword) {
        this.defaultPaxxword = defaultPaxxword;
    }
}
pamapi/src/main/java/com/pollex/pam/config/SecurityConfiguration.java
@@ -14,6 +14,8 @@
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;
import org.springframework.web.filter.CorsFilter;
import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;
@@ -58,11 +60,13 @@
        // @formatter:off
        http
            .csrf()
            .disable()
            .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
            .exceptionHandling()
                .authenticationEntryPoint(problemSupport)
                .accessDeniedHandler(problemSupport)
            .ignoringAntMatchers("/api/**")
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
        .and()
        .addFilterBefore(corsFilter, CsrfFilter.class)
        .exceptionHandling()
            .authenticationEntryPoint(problemSupport)
            .accessDeniedHandler(problemSupport)
        .and()
            .headers()
            .contentSecurityPolicy(jHipsterProperties.getSecurity().getContentSecurityPolicy())
pamapi/src/main/java/com/pollex/pam/security/jwt/TokenProvider.java
@@ -21,6 +21,7 @@
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import com.pollex.pam.business.config.AppProperties;
import com.pollex.pam.business.domain.TokenBlackList;
import com.pollex.pam.business.repository.TokenBlackListRepository;
@@ -44,6 +45,9 @@
    @Autowired
    TokenBlackListRepository tokenBlackListRepository;
    @Autowired
    AppProperties applicationProperties;
    public TokenProvider(JHipsterProperties jHipsterProperties) {
        byte[] keyBytes;
@@ -95,8 +99,7 @@
            .filter(auth -> !auth.trim().isEmpty())
            .map(SimpleGrantedAuthority::new)
            .collect(Collectors.toList());
        User principal = new User(claims.getSubject(), "", authorities);
        User principal = new User(claims.getSubject(), applicationProperties.getDefaultPaxxword(), authorities);
        UsernamePasswordAuthenticationToken authInfo = new UsernamePasswordAuthenticationToken(principal, token, authorities);
        authInfo.setDetails(claims.get(AUTHORITIES_DETAILS));
pamapi/src/main/java/com/pollex/pam/security/provider/EServiceAuthenticationProvider.java
@@ -65,6 +65,7 @@
            throw new RuntimeException("eService http error!, response http status code = " + responseEntity.getStatusCode());
        } catch (GeneralSecurityException e) {
            log.error("General Security SSL error!",e);
            throw new RuntimeException("General Security SSL error!");
        }
    }
pamapi/src/main/java/com/pollex/pam/security/token/OtpAuthenticationToken.java
@@ -9,7 +9,7 @@
    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
    private final OtpAccount principle;
    private transient OtpAccount principle;
    private String credentials;
    public OtpAuthenticationToken(OtpAccount principle, String credentials) {
pamapi/src/main/java/com/pollex/pam/service/CustomerService.java
@@ -60,16 +60,23 @@
            String account = getCustomerAccount(registDTO);
            OtpTmp otpTmp = otpTmpService.findByAccountAndIndexKey(account, registDTO.getIndexKey());
            if(otpTmp.getStatus() == OtpTmpStatusEnum.VERRIFIED) {
                Customer customer = customerDTOMapper.toCustomer(registDTO);
                customer.setDataFrom(DataFromEnum.PAM);
                save(customer);
                return customer;
            }else {
                throw new IllegalArgumentException("Invalid indexKey state error. IndexKey: "
                        + registDTO.getIndexKey()
                        + " => status: " + otpTmp.getStatus());
            }
            if(otpTmp!=null) {
                if(otpTmp.getStatus() == OtpTmpStatusEnum.VERRIFIED) {
                    Customer customer = customerDTOMapper.toCustomer(registDTO);
                    customer.setDataFrom(DataFromEnum.PAM);
                    save(customer);
                    return customer;
                }else {
                    throw new IllegalArgumentException("Invalid indexKey state error. IndexKey: "
                            + registDTO.getIndexKey()
                            + " => status: " + otpTmp.getStatus());
                }
            }else {
                throw new IllegalArgumentException("otp tmp not exist");
            }
        }
    }
pamapi/src/main/java/com/pollex/pam/service/OtpUtilService.java
@@ -60,8 +60,11 @@
    private void setVerrifiedOtpTmp(String account, String indexKey) {
        OtpTmp otpTmp = otpTmpService.findByAccountAndIndexKey(account, indexKey);
        otpTmp.setStatus(OtpTmpStatusEnum.VERRIFIED);
        otpTmpService.save(otpTmp);
        if(otpTmp!=null) {
            otpTmp.setStatus(OtpTmpStatusEnum.VERRIFIED);
            otpTmpService.save(otpTmp);
        }
    }
pamapi/src/main/java/com/pollex/pam/service/OtpWebService.java
@@ -22,8 +22,8 @@
    public OtpResponseDTO sendByPhone(String phone) {
        OtpWeb otpWS = getOtpWebService();
        log.debug("call OtpService sendOtpBySMS, url = {}, systemType = {}, service password = {}, phone = {}",
            applicationProperty.getOtpWebServiceUrl(), applicationProperty.getOtpWebServiceSystemType(), applicationProperty.getOtpWebServicePassword(), phone);
//        log.debug("call OtpService sendOtpBySMS, url = {}, systemType = {}, service password = {}, phone = {}",
//            applicationProperty.getOtpWebServiceUrl(), applicationProperty.getOtpWebServiceSystemType(), applicationProperty.getOtpWebServicePassword(), phone);
        StringArray result =
            otpWS.sendOtpBySMS(applicationProperty.getOtpWebServicePassword(), applicationProperty.getOtpWebServiceSystemType(), phone);
@@ -33,8 +33,8 @@
    public OtpResponseDTO sendByEmail(String email) {
        OtpWeb otpWS = getOtpWebService();
        log.debug("call OtpService sendByEmail, url = {}, systemType = {}, service password = {}, email = {}",
            applicationProperty.getOtpWebServiceUrl(), applicationProperty.getOtpWebServiceSystemType(), applicationProperty.getOtpWebServicePassword(), email);
//        log.debug("call OtpService sendByEmail, url = {}, systemType = {}, service password = {}, email = {}",
//            applicationProperty.getOtpWebServiceUrl(), applicationProperty.getOtpWebServiceSystemType(), applicationProperty.getOtpWebServicePassword(), email);
        StringArray result =
            otpWS.sendOtpByEmail(applicationProperty.getOtpWebServicePassword(), applicationProperty.getOtpWebServiceSystemType(), email);
@@ -44,8 +44,8 @@
    public OtpResponseDTO verifyOTP(String indexKey, String otpCode) {
        OtpWeb otpWS = getOtpWebService();
        log.debug("call OtpService verifyOTP, url = {}, systemType = {}, service password = {}, indexKey = {}, otpCode = {}",
            applicationProperty.getOtpWebServiceUrl(), applicationProperty.getOtpWebServiceSystemType(), applicationProperty.getOtpWebServicePassword(), indexKey, otpCode);
//        log.debug("call OtpService verifyOTP, url = {}, systemType = {}, service password = {}, indexKey = {}, otpCode = {}",
//            applicationProperty.getOtpWebServiceUrl(), applicationProperty.getOtpWebServiceSystemType(), applicationProperty.getOtpWebServicePassword(), indexKey, otpCode);
        StringArray result =
            otpWS.verifyOtp(applicationProperty.getOtpWebServicePassword(), applicationProperty.getOtpWebServiceSystemType(), indexKey, otpCode);
pamapi/src/main/java/com/pollex/pam/web/rest/AccountResource.java
@@ -65,17 +65,17 @@
        }
    }
    /**
     * {@code GET  /authenticate} : check if the user is authenticated, and return its login.
     *
     * @param request the HTTP request.
     * @return the login if the user is authenticated.
     */
    @GetMapping("/authenticate")
    public String isAuthenticated(HttpServletRequest request) {
        log.debug("REST request to check if the current user is authenticated");
        return request.getRemoteUser();
    }
//    /**
//     * {@code GET  /authenticate} : check if the user is authenticated, and return its login.
//     *
//     * @param request the HTTP request.
//     * @return the login if the user is authenticated.
//     */
//    @GetMapping("/authenticate")
//    public String isAuthenticated(HttpServletRequest request) {
//        log.debug("REST request to check if the current user is authenticated");
//        return request.getRemoteUser();
//    }
    /**
     * {@code GET  /account} : get the current user.
pamapi/src/main/java/com/pollex/pam/web/rest/EServiceResource.java
@@ -46,16 +46,19 @@
    @Autowired
    ConsultantService consultantService;
    @Autowired
    AesUtil aesUtil;
    @AuditLoggingInject(type = CONSULTANT_LOGIN)
    @PostMapping("/authenticate/{imgCode}")
    public ResponseEntity<UserJWTController.JWTToken> authorize(
            @RequestBody EServiceLoginVM eServiceLoginVM
            , HttpServletResponse response, HttpServletRequest request,
            @PathVariable String imgCode){
            @PathVariable String imgCode) throws Exception{
        
        
        String paswword = AesUtil.aesDecode(eServiceLoginVM.getPassword());
        String paswword = aesUtil.aesDecode(eServiceLoginVM.getPassword());
        if(!StringUtils.hasText(paswword)) {
            throw new OtpLoginFailException("密碼解密失敗");
        }
pamapi/src/main/java/com/pollex/pam/web/rest/TestLoginResource.java
@@ -71,49 +71,49 @@
        return new ResponseEntity<>(otpResponseDTO, HttpStatus.OK);
    }
    @GetMapping("/byEService")
    public ResponseEntity<EServiceResponse> loginByEService(@RequestParam("account") String account, @RequestParam("password") String password) throws Exception {
        RestTemplate restTemplate = getTrustAllRestTemplate();
        settingMessageConvertesToSpecifyType(restTemplate, MediaType.ALL);
//    @GetMapping("/byEService")
//    public ResponseEntity<EServiceResponse> loginByEService(@RequestParam("account") String account, @RequestParam("password") String password) throws Exception {
//        RestTemplate restTemplate = getTrustAllRestTemplate();
//        settingMessageConvertesToSpecifyType(restTemplate, MediaType.ALL);
//
//        String urlTemplate = UriComponentsBuilder.fromHttpUrl(applicationProperty.geteServiceLoginUrl())
//            .queryParam("func", applicationProperty.geteServiceLoginFunc())
//            .queryParam("id", account)
//            .queryParam("pin", password)
//            .queryParam("pwd", password)
//            .queryParam("sys", applicationProperty.geteServiceLoginSys())
//            .queryParam("transactionId", UUID.randomUUID().toString())
//            .encode().toUriString();
//
//        log.debug("http get loginByEService, url = {}", urlTemplate);
//
//        HttpHeaders headers = new HttpHeaders();
//        headers.setContentType(MediaType.APPLICATION_JSON);
//
//        HttpEntity<String> entity = new HttpEntity<>(headers);
//        return restTemplate.exchange(urlTemplate, HttpMethod.GET, entity, EServiceResponse.class);
//    }
        String urlTemplate = UriComponentsBuilder.fromHttpUrl(applicationProperty.geteServiceLoginUrl())
            .queryParam("func", applicationProperty.geteServiceLoginFunc())
            .queryParam("id", account)
            .queryParam("pin", password)
            .queryParam("pwd", password)
            .queryParam("sys", applicationProperty.geteServiceLoginSys())
            .queryParam("transactionId", UUID.randomUUID().toString())
            .encode().toUriString();
//    private void settingMessageConvertesToSpecifyType(RestTemplate restTemplate, MediaType mediaType) {
//        List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
//        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
//        converter.setSupportedMediaTypes(Collections.singletonList(mediaType));
//        messageConverters.add(converter);
//        restTemplate.setMessageConverters(messageConverters);
//    }
        log.debug("http get loginByEService, url = {}", urlTemplate);
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity<String> entity = new HttpEntity<>(headers);
        return restTemplate.exchange(urlTemplate, HttpMethod.GET, entity, EServiceResponse.class);
    }
    private void settingMessageConvertesToSpecifyType(RestTemplate restTemplate, MediaType mediaType) {
        List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
        MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
        converter.setSupportedMediaTypes(Collections.singletonList(mediaType));
        messageConverters.add(converter);
        restTemplate.setMessageConverters(messageConverters);
    }
    private RestTemplate getTrustAllRestTemplate() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
        SSLContext sslContext = SSLContexts.custom()
            .loadTrustMaterial(null, (X509Certificate[] x509Certs, String s) -> true)
            .build();
        SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
        CloseableHttpClient httpClient = HttpClients.custom()
            .setSSLSocketFactory(csf)
            .build();
        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
        requestFactory.setHttpClient(httpClient);
        requestFactory.setConnectTimeout(300000);
        requestFactory.setReadTimeout(300000);
        return new RestTemplate(requestFactory);
    }
//    private RestTemplate getTrustAllRestTemplate() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
//        SSLContext sslContext = SSLContexts.custom()
//            .loadTrustMaterial(null, (X509Certificate[] x509Certs, String s) -> true)
//            .build();
//        SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
//        CloseableHttpClient httpClient = HttpClients.custom()
//            .setSSLSocketFactory(csf)
//            .build();
//        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
//        requestFactory.setHttpClient(httpClient);
//        requestFactory.setConnectTimeout(300000);
//        requestFactory.setReadTimeout(300000);
//        return new RestTemplate(requestFactory);
//    }
}
pamapi/src/main/java/com/pollex/pam/web/rest/UserResource.java
@@ -93,39 +93,39 @@
        this.mailService = mailService;
    }
    /**
     * {@code POST  /admin/users}  : Creates a new user.
     * <p>
     * Creates a new user if the login and email are not already used, and sends an
     * mail with an activation link.
     * The user needs to be activated on creation.
     *
     * @param userDTO the user to create.
     * @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new user, or with status {@code 400 (Bad Request)} if the login or email is already in use.
     * @throws URISyntaxException if the Location URI syntax is incorrect.
     * @throws BadRequestAlertException {@code 400 (Bad Request)} if the login or email is already in use.
     */
    @PostMapping("/users")
    @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")")
    public ResponseEntity<User> createUser(@Valid @RequestBody AdminUserDTO userDTO) throws URISyntaxException {
        log.debug("REST request to save User : {}", userDTO);
        if (userDTO.getId() != null) {
            throw new BadRequestAlertException("A new user cannot already have an ID", "userManagement", "idexists");
            // Lowercase the user login before comparing with database
        } else if (userRepository.findOneByLogin(userDTO.getLogin().toLowerCase()).isPresent()) {
            throw new LoginAlreadyUsedException();
        } else if (userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()).isPresent()) {
            throw new EmailAlreadyUsedException();
        } else {
            User newUser = userService.createUser(userDTO);
            mailService.sendCreationEmail(newUser);
            return ResponseEntity
                .created(new URI("/api/admin/users/" + newUser.getLogin()))
                .headers(HeaderUtil.createAlert(applicationName, "userManagement.created", newUser.getLogin()))
                .body(newUser);
        }
    }
//    /**
//     * {@code POST  /admin/users}  : Creates a new user.
//     * <p>
//     * Creates a new user if the login and email are not already used, and sends an
//     * mail with an activation link.
//     * The user needs to be activated on creation.
//     *
//     * @param userDTO the user to create.
//     * @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new user, or with status {@code 400 (Bad Request)} if the login or email is already in use.
//     * @throws URISyntaxException if the Location URI syntax is incorrect.
//     * @throws BadRequestAlertException {@code 400 (Bad Request)} if the login or email is already in use.
//     */
//    @PostMapping("/users")
//    @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")")
//    public ResponseEntity<User> createUser(@Valid @RequestBody AdminUserDTO userDTO) throws URISyntaxException {
//        log.debug("REST request to save User : {}", userDTO);
//
//        if (userDTO.getId() != null) {
//            throw new BadRequestAlertException("A new user cannot already have an ID", "userManagement", "idexists");
//            // Lowercase the user login before comparing with database
//        } else if (userRepository.findOneByLogin(userDTO.getLogin().toLowerCase()).isPresent()) {
//            throw new LoginAlreadyUsedException();
//        } else if (userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()).isPresent()) {
//            throw new EmailAlreadyUsedException();
//        } else {
//            User newUser = userService.createUser(userDTO);
//            mailService.sendCreationEmail(newUser);
//            return ResponseEntity
//                .created(new URI("/api/admin/users/" + newUser.getLogin()))
//                .headers(HeaderUtil.createAlert(applicationName, "userManagement.created", newUser.getLogin()))
//                .body(newUser);
//        }
//    }
    /**
     * {@code PUT /admin/users} : Updates an existing User.
pamapi/src/main/resources/config/application-dev.yml
@@ -140,3 +140,5 @@
    sender-email: noreply@pcalife.com.tw
    method: 'POLLEX_GMAIL'
  file-folder-path: C://pam_file
  aes-key: PAMKEY1234567890
  default-paxxword:
pamapi/src/main/resources/config/application-pollex.yml
@@ -32,9 +32,13 @@
      indent-output: true
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:postgresql://dev.pollex.com.tw:5433/pam_p2
    #url: jdbc:postgresql://localhost:5432/omo?currentSchema=public
    username: pamadmin
    password: pamadmin
    hikari:
      poolName: Hikari
    jndi-name: Pam
      maximum-pool-size: 500
  jpa:
    database-platform: tech.jhipster.domain.util.FixedPostgreSQL10Dialect
  liquibase:
@@ -136,3 +140,5 @@
    sender-email: noreply@pcalife.com.tw
    method: 'POLLEX_GMAIL'
  file-folder-path: C://pam_file
  aes-key: PAMKEY1234567890
  default-paxxword:
pamapi/src/main/resources/config/application-prod.yml
@@ -153,3 +153,5 @@
    sender-email: noreply@pcalife.com.tw
    method: 'PAM_EMAIL_SERVICE'
  file-folder-path: /sfs_omo/AgentPhoto/
  aes-key: PAMKEY1234567890
  default-paxxword:
pamapi/src/main/resources/config/application-sit.yml
@@ -130,3 +130,5 @@
    sender-email: noreply@pcalife.com.tw
    method: 'PAM_EMAIL_SERVICE'
  file-folder-path: /sfs_omo/AgentPhoto/
  aes-key: PAMKEY1234567890
  default-paxxword:
pamapi/src/main/resources/config/application-tls.yml
@@ -13,7 +13,11 @@
    key-store-password: password
    key-store-type: PKCS12
    key-alias: selfsigned
    ciphers: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
    ciphers:
        - TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
        - TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
    enabled-protocols: TLSv1.2
  http2:
    enabled: true
pamapi/src/main/resources/config/application-uat.yml
@@ -130,3 +130,5 @@
    sender-email: noreply@pcalife.com.tw
    method: 'PAM_EMAIL_SERVICE'
  file-folder-path: /sfs_omo/AgentPhoto/
  aes-key: PAMKEY1234567890
  default-paxxword:
pamapi/src/test/java/com/pollex/pam/web/rest/AccountResourceIT.java
@@ -77,33 +77,33 @@
            .andExpect(content().string(TEST_USER_LOGIN));
    }
    @Test
    void testGetExistingAccount() throws Exception {
        Set<String> authorities = new HashSet<>();
        authorities.add(AuthoritiesConstants.ADMIN);
        AdminUserDTO user = new AdminUserDTO();
        user.setLogin(TEST_USER_LOGIN);
        user.setFirstName("john");
        user.setLastName("doe");
        user.setEmail("john.doe@jhipster.com");
        user.setImageUrl("http://placehold.it/50x50");
        user.setLangKey("en");
        user.setAuthorities(authorities);
        userService.createUser(user);
        restAccountMockMvc
            .perform(get("/api/account").accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk())
            .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
            .andExpect(jsonPath("$.login").value(TEST_USER_LOGIN))
            .andExpect(jsonPath("$.firstName").value("john"))
            .andExpect(jsonPath("$.lastName").value("doe"))
            .andExpect(jsonPath("$.email").value("john.doe@jhipster.com"))
            .andExpect(jsonPath("$.imageUrl").value("http://placehold.it/50x50"))
            .andExpect(jsonPath("$.langKey").value("en"))
            .andExpect(jsonPath("$.authorities").value(AuthoritiesConstants.ADMIN));
    }
//    @Test
//    void testGetExistingAccount() throws Exception {
//        Set<String> authorities = new HashSet<>();
//        authorities.add(AuthoritiesConstants.ADMIN);
//
//        AdminUserDTO user = new AdminUserDTO();
//        user.setLogin(TEST_USER_LOGIN);
//        user.setFirstName("john");
//        user.setLastName("doe");
//        user.setEmail("john.doe@jhipster.com");
//        user.setImageUrl("http://placehold.it/50x50");
//        user.setLangKey("en");
//        user.setAuthorities(authorities);
//        userService.createUser(user);
//
//        restAccountMockMvc
//            .perform(get("/api/account").accept(MediaType.APPLICATION_JSON))
//            .andExpect(status().isOk())
//            .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE))
//            .andExpect(jsonPath("$.login").value(TEST_USER_LOGIN))
//            .andExpect(jsonPath("$.firstName").value("john"))
//            .andExpect(jsonPath("$.lastName").value("doe"))
//            .andExpect(jsonPath("$.email").value("john.doe@jhipster.com"))
//            .andExpect(jsonPath("$.imageUrl").value("http://placehold.it/50x50"))
//            .andExpect(jsonPath("$.langKey").value("en"))
//            .andExpect(jsonPath("$.authorities").value(AuthoritiesConstants.ADMIN));
//    }
    @Test
    void testGetUnknownAccount() throws Exception {