PAMapp/.env.dev
¤ñ¹ï·sÀÉ®× @@ -0,0 +1 @@ BASE_URL='http://localhost:8080/api' PAMapp/.env.uat
¤ñ¹ï·sÀÉ®× @@ -0,0 +1 @@ BASE_URL='https://vtwlifeopensysuat.pru.intranet.asia/pamapi/api' PAMapp/assets/scss/vendors/_elementUI.scss
@@ -10,3 +10,4 @@ @import './elementUI/tag'; @import './elementUI/pagination'; @import './elementUI/dialog'; @import './elementUI/messageBox'; PAMapp/assets/scss/vendors/elementUI/_button.scss
@@ -24,6 +24,7 @@ &:hover{ background-color: $MID_GREY; border-color: $MID_GREY; color: $PRIMARY_WHITE; } } } @@ -106,7 +107,7 @@ border:none; font-size: 20px; } } .radio-btn{ display: flex; PAMapp/assets/scss/vendors/elementUI/_messageBox.scss
¤ñ¹ï·sÀÉ®× @@ -0,0 +1,28 @@ .el-message-box__wrapper{ display: flex; align-items: center; justify-content: center; .pam-message-box.el-message-box{ display: flex; flex-direction: column; justify-content: space-between; width:300px; height: 150px; .el-message-box__content{ padding: 15px; .el-message-box__message p{ text-align: center; color: $PRIMARY_BLACK; letter-spacing: 2px; font-size: 20px; } } .el-message-box__btns{ text-align: center; } .el-button.el-button--primary{ color: $PRIMARY_WHITE; } } } PAMapp/assets/ts/api/consultant.ts
@@ -1,10 +1,26 @@ import { service } from '~/assets/ts/api/share'; import { AxiosResponse } from 'axios'; import { AppointmentDetail } from '../models/AppointmentDetail'; import { ConsultantLoginInfo } from '../models/ConsultantLoginInfo'; // 顧客ç»å ¥(TODO: OTPèªèéç¼å æ«æä½¿ç¨) export function login(user: any) { return service.post('/authenticate', user) } // 顧客ç»å ¥-ç¼éOTP export function sendOtp(loginInfo: LoginRequest) { return service.post<OtpInfo>('/otp/sendOtp', loginInfo).then(res => res.data) } // 顧客ç»å ¥-é©èotp並ç»å ¥ export function loginVerify(loginVerify: LoginVerify) { return service.post('/otp/verify', loginVerify) } // 顧客註å export function register(registerInfo: RegisterInfo) { return service.post('/otp/register', registerInfo) } // æ¨è¦ä¿éªé¡§å @@ -76,13 +92,8 @@ } // é¡§åç»å ¥ export function logInToConsultant(consultantDto:ConsultantLoginInfo):Promise<boolean>{ console.log('consultantDto',consultantDto); return new Promise((resolve, reject)=>{ setTimeout(()=>{ resolve(true); },1000) }) export function logInToConsultant(consultantDto:ConsultantLoginInfo):Promise<AxiosResponse<RequestOfLoginSuccess>>{ return service.post('/eService/authenticate',consultantDto); } // åå¾é ç´å®ç´°ç¯ @@ -91,11 +102,6 @@ Authorization: 'Bearer ' + localStorage.getItem('id_token') } return service.get('/appointment/getDetail/'+apointmentId, {headers}) } export interface ConsultantLoginInfo{ account:string, password:string, verificationCode:string, } export interface Consultants { agentNo: string, @@ -163,4 +169,41 @@ seniority: string; new: boolean; } export interface RequestOfLoginSuccess{ id_token:string; } export interface LoginRequest { /** "SMS"=ææ©ï¼"EMAIL"=email */ loginType: string, /** è¥loginTypeå¡«SMSå該æ¬å¸¶å ¥ææ©ãEMAILåå¸¶å ¥éµä»¶ä¿¡ç®± */ account: string, } export interface OtpInfo { /** ç¨æ¼å¸¶å ¥otpèªèæ */ indexKey: string, /** Otpæ¯å¦ææåç¼é */ success: boolean, failCode: string, failReason: string, } export interface LoginVerify { /** å¯å¸¶å ¥ææ©æemail */ account: string, /** ç±otpçapiåçindex key */ indexKey: string, /** ç±ææ©æä¿¡ç®±æ¶å°çèªè碼 */ otpCode: string } export interface RegisterInfo { phone?: string, email?: string, indexKey: string, otpCode: string, name: string, /** "SMS"ï¼Otpç¼éææ©ï¼"EMAIL":Otpç¼email */ contactType: string } PAMapp/assets/ts/api/share.ts
@@ -1,31 +1,49 @@ import axios from 'axios'; import { AxiosRequestConfig, AxiosError, AxiosResponse } from 'axios'; import { AxiosRequestConfig, AxiosError, AxiosResponse } from 'axios'; import { MessageBox } from 'element-ui'; export const service = axios.create({ baseURL: process.env.BASE_URL }) service.interceptors.request.use(function (config: AxiosRequestConfig) { loadingStart(); service.interceptors.request.use( (config:AxiosRequestConfig)=>{ loadingStart(); return config; }, function (error: AxiosError) { loadingFinish(); return Promise.reject(error); }); } ); service.interceptors.response.use(function (response: AxiosResponse) { loadingFinish(); return response; }, function (error: AxiosError) { loadingFinish(); service.interceptors.response.use( (response:AxiosResponse)=>{ loadingFinish(); return response; // maybe can use response.data }, (error:AxiosError)=>{ loadingFinish(); if (error.config.url !== '/otp/verify') { openErrorMessage(); } return Promise.reject(error); }); } ); function loadingStart(): void { setTimeout(() => { window.$nuxt.$loading.start(); }); } function loadingFinish(): void { window.$nuxt.$loading.finish(); } function openErrorMessage():void{ MessageBox({ message: '系統ç¼çé¯èª¤', showClose:false, showConfirmButton:true, confirmButtonText:'確èª', customClass:'pam-message-box', closeOnClickModal:false, }); } PAMapp/assets/ts/models/ConsultantLoginInfo.ts
¤ñ¹ï·sÀÉ®× @@ -0,0 +1,5 @@ export interface ConsultantLoginInfo { username: string; password: string; verificationCode: string; } PAMapp/assets/ts/models/enum/Role.ts
¤ñ¹ï·sÀÉ®× @@ -0,0 +1,5 @@ export enum Role{ ADMIN = 'admin', USER = 'user', NOT_LOGIN = '' } PAMapp/components/BackActionBar.vue
@@ -9,12 +9,13 @@ <script lang="ts"> import { namespace } from 'nuxt-property-decorator'; import { Vue, Component,} from 'vue-property-decorator'; import { Role } from '~/assets/ts/models/enum/Role'; import * as _ from 'lodash'; import { Role } from './NavBar.vue'; const localStorage = namespace('localStorage'); const roleStorage = namespace('localStorage'); @Component export default class UiCarousel extends Vue { @localStorage.Getter currentRole!:string; @roleStorage.Getter currentRole!:string; get label(): string { if (this.$route.name) { const routeName = this.$route.name.split('-')[0]; PAMapp/components/NavBar.vue
@@ -9,7 +9,7 @@ <div class="pam-header__action-bar"> <i class="icon-bell text--dark-blue cursor--pointer fix-chrome-click--issue" @click="$router.push('/notification')"></i> <el-dropdown :class="{'is-open':isOpenDropdown}" <el-dropdown :class="{'is-open':isOpenDropdown}" ref="dropdown" trigger="click" @command="routerNavigateTo"> @@ -34,13 +34,15 @@ <script lang="ts"> import { Vue, Component } from 'vue-property-decorator'; import { namespace } from 'nuxt-property-decorator'; import { Role } from '~/assets/ts/models/enum/Role'; import * as _ from 'lodash'; const localStorage = namespace('localStorage'); const roleStorage = namespace('localStorage'); @Component export default class NavBar extends Vue { @localStorage.Mutation storageClear!: () => void; @localStorage.Getter idToken!: string | null; @localStorage.Getter currentRole!: string | null; @roleStorage.Mutation storageClear!: () => void; @roleStorage.Getter idToken!: string | null; @roleStorage.Getter currentRole!: string | null; navBarList = [{ authorityOfRoleList: [Role.NOT_LOGIN], @@ -99,11 +101,6 @@ this.storageClear(); _.isEqual(this.$route.name, 'index') ? location.reload() : this.$router.push('/'); } } export enum Role { USER = 'user', ADMIN = 'admin', NOT_LOGIN = '', } </script> @@ -187,7 +184,7 @@ color: $PRIMARY_WHITE; } } } } @include desktop { .pam-header { @@ -205,7 +202,7 @@ .pam-header__title { display: flex; justify-content: start; justify-content: flex-start; align-items: center; border: none; padding-left: 30px; PAMapp/nuxt.config.js
@@ -50,7 +50,9 @@ // https://go.nuxtjs.dev/typescript '@nuxt/typescript-build', '@nuxtjs/axios', '@nuxtjs/style-resources' '@nuxtjs/style-resources', ['@nuxtjs/dotenv', { filename: '.env.' + process.env.ENV }], ], // Modules: https://go.nuxtjs.dev/config-modules @@ -74,8 +76,5 @@ document.documentElement.scrollTop = 0; } } }, env: { BASE_URL: 'http://localhost:8080/api' } } PAMapp/package.json
@@ -3,10 +3,13 @@ "version": "1.0.0", "private": true, "scripts": { "dev": "nuxt", "dev": "cross-env ENV=dev nuxt", "uat": "cross-env ENV=uat nuxt", "build": "nuxt build", "start": "nuxt start", "generate": "nuxt generate", "generate.dev": "cross-env ENV=dev nuxt generate", "generate.uat": "cross-env ENV=uat nuxt generate", "test": "jest" }, "dependencies": { @@ -26,10 +29,12 @@ "devDependencies": { "@nuxt/types": "^2.15.8", "@nuxt/typescript-build": "^2.1.0", "@nuxtjs/dotenv": "^1.4.1", "@types/lodash": "^4.14.176", "@vue/test-utils": "^1.2.2", "babel-core": "7.0.0-bridge.0", "babel-jest": "^27.3.0", "cross-env": "^7.0.3", "fibers": "^5.0.0", "jest": "^27.3.0", "sass": "1.43.2", PAMapp/pages/consultantLogin/index.vue
@@ -6,11 +6,11 @@ <div class="pam-consultant-login__title">帳è</div> <div class="position-r mt-10"> <input type="text" v-model="consultantDto.account" v-model="consultantDto.username" class="pam-consultant-login__input" placeholder="è¼¸å ¥eService帳è"> <div class="pam-consultant-login__inputIcon text--primary cursor--pointer" @click="recordAccount"> <div class="pam-consultant-login__inputIcon text--primary cursor--pointer fix-chrome-click--issue" @click="isRemember = !isRemember"> <i :class="[isRemember ? 'icon-checkbox-1' : 'icon-checkbox','pr-5']"></i> è¨ä½ </div> @@ -19,7 +19,7 @@ <div class="pam-paragraph"> <div class="pam-consultant-login__title "> <div>å¯ç¢¼</div> <a class="pam-consultant-login__forgot-password cursor--pointer" <a class="pam-consultant-login__forgot-password cursor--pointer fix-chrome-click--issue" :href="forgotPasswordLink" target="_blank" rel="ä¿èª 人壽"> @@ -31,16 +31,16 @@ v-model="consultantDto.password" class="pam-consultant-login__input" placeholder="è¼¸å ¥eServiceå¯ç¢¼"> <div class="pam-consultant-login__inputIcon cursor--pointer" <div class="pam-consultant-login__inputIcon cursor--pointer fix-chrome-click--issue" @click="isShowPassword = !isShowPassword"> <i :class="[isShowPassword ? 'icon-eye-1 fs-25' : 'icon-eye' , 'text--primary']"></i> <i :class="[isShowPassword ? 'icon-eye':'icon-eye-1 fs-25', 'text--primary']"></i> </div> </div> </div> <div class="pam-paragraph"> <div class="pam-consultant-login__title"> <div>é©è碼</div> <div class="text--dark-blue fs-16 cursor--pointer" <div class="text--dark-blue fs-16 cursor--pointer fix-chrome-click--issue" @click="regenerateCode">éæ°ç¢ç</div> </div> <div class="pam-consultant-login__verifyBlock mt-10"> @@ -50,13 +50,14 @@ class="pam-consultant-login__input"> </div> <div class="pam-consultant-login__verifyImg"> <img src="~/assets/images/logo.png" alt="é©è碼"> <img src="~/assets/images/logo.png" alt="é©è碼"> </div> </div> </div> <div class="pam-consultant-login__confirmBlock pam-paragraph"> <button class="pam-consultant-login__confirm cursor--pointer" @click="fakeLogin">éåº</button> <button class="pam-consultant-login__confirm cursor--pointer fix-chrome-click--issue" @click="loginWithConsultant">éåº</button> </div> </div> </div> @@ -64,70 +65,73 @@ <script lang="ts"> import { namespace } from 'nuxt-property-decorator'; import { Vue, Component} from 'vue-property-decorator'; import { getForgotPasswordLink , getVerificationCodeImg , login } from '~/assets/ts/api/consultant'; import { Role } from '../../components/NavBar.vue'; import { Role } from '~/assets/ts/models/enum/Role'; import { Vue, Component } from 'vue-property-decorator'; import { getForgotPasswordLink, getVerificationCodeImg, logInToConsultant } from '~/assets/ts/api/consultant'; const localStorage = namespace('localStorage'); const roleStorage = namespace('localStorage'); @Component({ layout: 'home' }) export default class ConsultantLogin extends Vue { @localStorage.Mutation storageIdToken!: (token:string) => void; @localStorage.Mutation storageRole!: (role:string) => void; @roleStorage.Mutation storageIdToken!: (token: string) => void; @roleStorage.Mutation storageRole!: (role: string) => void; isRemember = false; isShowPassword = false; forgotPasswordLink = ''; // å°æª imgOfVerificationCode = ''; // å°æª consultantDto = { account: '', username: '', password: '', verificationCode: '', } forgotPasswordLink = ''; imgOfVerificationCode=''; mounted() { this.getRememberUserName(); this.regenerateCode(); this.getLinkOfForgotPassword(); }; private getLinkOfForgotPassword():void{ getForgotPasswordLink().then(link=>{ console.log('link',link); private getRememberUserName(): void { const username = localStorage.getItem('consultantUserName') if (username) { this.consultantDto.username = username; this.isRemember = true; } } private getLinkOfForgotPassword(): void { getForgotPasswordLink().then(link => { this.forgotPasswordLink = link; }); }; recordAccount(): void { this.isRemember = !this.isRemember; if (this.isRemember) { console.log('sotre account'); } }; regenerateCode(): void { getVerificationCodeImg().then((imgOfbase64:any)=>{ public regenerateCode(): void { getVerificationCodeImg().then(imgOfbase64 => { this.imgOfVerificationCode = imgOfbase64; }); }; // loginWithConsultant():void{ // console.log('consultantDto',this.consultantDto); // logInToConsultant(this.consultantDto).then(res=>{ // localStorage.setItem('roleOfState',Role.CONSULTANT); // this.$router.push('/myAppointmentList/appointmentList'); // }); // } fakeLogin(): void { const user = { username: 'admin', password: 'admin' } login(user).then((res) => { public loginWithConsultant(): void { this.recordAccount(); logInToConsultant(this.consultantDto).then(res => { this.storageIdToken(res.data.id_token); this.storageRole(Role.ADMIN); this.$router.push('/myAppointmentList/appointmentList'); }) }, (error) => { this.consultantDto.password = ''; this.consultantDto.verificationCode = ''; }); } private recordAccount(): void { localStorage.setItem('consultantUserName', this.isRemember ? this.consultantDto.username : ''); }; }; </script> <style lang="scss" scoped> .mt-20 { @@ -187,11 +191,13 @@ right: 15px; } } &__forgot-password{ &__forgot-password { color: $PRIMARY_RED; text-decoration:none; text-decoration: none; font-size: 16px; } &__verifyBlock { display: flex; justify-content: space-between; @@ -201,7 +207,7 @@ width: 126px; border: 1px black solid; height: 50px; img{ img { width: 100%; height: 100%; } PAMapp/pages/login/index.vue
@@ -1,105 +1,129 @@ <template> <div class="pam-login-page"> <div class="text--middle">ç»å ¥</div> <div class='mb-30'> <div class="mdTxt">ç»å ¥æ¹å¼</div> <div class="pam-field-title__hint mt-5 mb-10" >é¡§å尿以æ¤{{connectDevice === 'MOBILE' ? 'ææ©è碼' : 'Email'}}çºä¸»è¦é ç´è«®è©¢è¯ç¹«æ¹å¼</div> <div class="pam-paragraph"> <div class="mdTxt"> ç¶å®æ¹å¼ </div> <div class="pam-tags"> <el-row type="flex" class="pt-10"> <el-button :class="{ 'active': connectDevice === 'MOBILE'}" @click="connectDevice = 'MOBILE'">ææ©è碼</el-button> <el-button :class="{ 'active': connectDevice === 'EMAIL'}" @click="connectDevice = 'EMAIL'">Email</el-button> </el-row> </div> <el-row type="flex" class="pt-10" v-show="connectDevice === 'MOBILE'"> <input class="pam-input" :class="{ 'is-invalid': !phoneNumber }" v-model="phoneNumber" placeholder="è«è¼¸å ¥ææ©è碼" > </el-row> <el-row class="pt-10" v-show="connectDevice === 'EMAIL'"> <input class="pam-input" :class="{ 'is-invalid': !phoneNumber }" v-model="email" placeholder="è«è¼¸å ¥ Email å°å" > </el-row> <div class="pt-30" v-show="showPhoneOtpCodeField"> <el-row type="flex" justify="space-between"> <div class="mdTxt">è¼¸å ¥é©è碼</div> <div class="otp-count-timer"> 13:50 </div> <div class="pam-tags"> <el-row type="flex" class="pt-30"> <el-button :class="{ 'active': connectDevice === 'MOBILE'}" @click="connectDevice = 'MOBILE'">ææ©è碼</el-button> <el-button :class="{ 'active': connectDevice === 'EMAIL'}" @click="connectDevice = 'EMAIL'">Email</el-button> </el-row> </div> <el-row class="pt-10"> <div class="pam-inputs mb-10"> <div class="pt-10" v-show="connectDevice === 'MOBILE'"> <input class="pam-input" :class="{ 'is-invalid': !phoneValid }" v-model="phoneNumber" placeholder="è«è¼¸å ¥ææ©è碼"> <div class="error mt-5 mb-5"> <span v-show="!phoneValid">ææ©èç¢¼æ ¼å¼æèª¤</span> </div> </div> <div class="pt-10" v-show="connectDevice === 'EMAIL'"> <input class="pam-input" :class="{ 'is-invalid': !otpCode 'is-invalid': !emailValid }" v-model="otpCode" placeholder="è«è¼¸å ¥é©è碼" v-model="email" placeholder="è«è¼¸å ¥ Email å°å" > <div class="error mt-5 mb-5"> <span v-show="!emailValid">Emailæ ¼å¼æèª¤</span> </div> </div> </div> <!-- mobile é©è碼 --> <template v-if="connectDevice === 'MOBILE'"> <div v-show="showPhoneOtpCodeField"> <el-row type="flex" justify="space-between"> <div class="mdTxt">è¼¸å ¥é©è碼</div> <div class="otp-count-timer"> {{otpCounter}} </div> </el-row> <el-row class="mb-30"> <input class="pam-input mt-10" :class="{ 'is-invalid': !otpCode }" v-model="otpCode" placeholder="è«è¼¸å ¥é©è碼" > </el-row> <el-row> <el-button :disabled="!phoneNumber || otpResendCounter !== 0 || !phoneValid" @click="resentOtp('MOBILE')" icon="icon-arrow" > éç¼é©è碼<span class="pam-field-title__hint pl-5">({{ otpResendCounter }})</span> </el-button> </el-row> </div> <el-row> <el-button v-if="onPhoneVerifyStep === 'APPLY_OTP'" :disabled="!phoneNumber || !phoneValid" @click="applyOtpVerification('MOBILE')" icon="icon-arrow" > ç¼éé©è碼 </el-button> </el-row> <el-row class="pt-10"> <button class="pam-otp-resend-btn" :class="{'disabled': true}"> <i class="icon-arrow"></i> éç¼é©è碼({{ otpResendCounter }}) </button> </el-row> </div> </template> <div v-show="showEmailVerifyField"> <el-row class="pt-10"> <button class="pam-otp-resend-btn" :class="{'disabled': onEmailVerifyResendStatus === 'CANNOT_RESEND'}"> <i class="icon-arrow"></i> éç¼é©è碼({{ emailResendCounter }}) </button> <!-- email é©è碼 --> <template v-if="connectDevice === 'EMAIL'"> <el-row v-show="showEmailVerifyField"> <el-button :disabled="!email || emailResendCounter !== 0 || !emailValid" icon="icon-arrow" @click="resentOtp('EMAIL')" > éç¼é©è碼<span class="pam-field-title__hint pl-5">({{ emailResendCounter }})</span> </el-button> <div class="mt-10 smTxt_bold text--primary">! è«ç¨çï¼æ°çEmailå¾ åç¨</div> </el-row> </div> <el-row v-show="!showEmailVerifyField"> <el-button :disabled="!email || !emailValid" @click="applyOtpVerification('EMAIL')" icon="icon-arrow" > ç¼éé©è碼 </el-button> </el-row> </template> </div> <el-row type="flex" justify="center" class="pam-login-page__action-bar"> <div v-if="connectDevice === 'MOBILE'"> <el-button type="primary" v-if="onPhoneVerifyStep === 'APPLY_OTP'" :disabled="!phoneNumber" @click="applyOtpVerification"> ç¼éé©è碼 </el-button> <el-button type="primary" v-if="connectDevice === 'MOBILE' && onPhoneVerifyStep === 'INPUT_OTP'" :disabled="!otpCode" @click="registerDialogVisable = true"> éåº </el-button> </div> <el-row type="flex" justify="center" class="pam-login-page__action-bar mt-30"> <el-button type="primary" v-if="connectDevice === 'MOBILE' && onPhoneVerifyStep === 'INPUT_OTP'" :disabled="!otpCode || !phoneNumber || !phoneValid" @click="phoneLogin"> éåº </el-button> </el-row> <el-dialog @@ -213,41 +237,98 @@ </span> </el-dialog> <PopUpFrame class="pam-popUpFrame" :isOpen.sync="emailOtpConfirmVisable"> <div class="pam-popUp-title text--center">å·²å°é©èè¨æ¯ç¼éè³</div> <div class="pam-popUp-title text--center">{{email}}</div> <div class="pam-popUp-title text--center">è«æ¥çé»åéµä»¶ä¸¦å®æé©èæµç¨</div> <div class="pam-popUp-confirm-bolck pam-paragraph"> <div class="text--center"> <el-button type="primary" @click="emailOtpConfirmVisable = false" >æç¥éäº</el-button> </div> </div> </PopUpFrame> <PopUpFrame class="pam-popUpFrame" :isOpen.sync="registerSuccessConfirmVisable"> <div class="pam-popUp-title text--center"> æ¡è¿æ¨ç»å ¥æåï¼å¦æ¨é ç´è«®è©¢ï¼é¡§åæä»¥æ¨çä¸ç{{ connectDevice === 'MOBILE' ? 'ææ©è碼' : 'Email'}}èæ¨è¯ç¹« </div> <div class="pam-popUp-confirm-bolck pam-paragraph"> <div class="text--center"> <el-button type="primary" @click="confirmApplySuccess" >æç¥éäº</el-button> </div> </div> </PopUpFrame> <el-button class="mt-30" @click="fakeLogin">客æ¶ç»å ¥</el-button> <PopUpFrame class="pam-popUpFrame" :isOpen.sync="phoneSuccessConfirmVisable"> <div class="pam-popUp-title text--center mb-50" >æ¡è¿æ¨ç»å ¥æå</div> <div class="pam-popUp-confirm-bolck pam-paragraph"> <div class="text--center"> <el-button type="primary" @click="confirmApplySuccess" >æç¥éäº</el-button> </div> </div> </PopUpFrame> </div> </template> <script lang="ts"> import { namespace } from 'nuxt-property-decorator'; import { Vue, Component } from 'vue-property-decorator'; import { login } from '~/assets/ts/api/consultant'; import { Role } from '../../components/NavBar.vue'; const localStorage = namespace('localStorage'); import { LoginRequest, loginVerify, OtpInfo, register, RegisterInfo, sendOtp } from '~/assets/ts/api/consultant'; import { Role } from '~/assets/ts/models/enum/Role'; const roleStorage = namespace('localStorage'); @Component export default class Login extends Vue { @localStorage.Mutation storageIdToken!: (token:string) => void; @localStorage.Mutation storageRole!: (role:string) => void; @roleStorage.Mutation storageIdToken!: (token:string) => void; @roleStorage.Mutation storageRole!: (role:string) => void; connectDevice: 'MOBILE' | 'EMAIL' = 'MOBILE'; phoneNumber = ''; otpCode = ''; onPhoneVerifyStep: 'APPLY_OTP' | 'INPUT_OTP' | 'SUBMIT_OTP' = 'APPLY_OTP'; otpCounter = '15:00'; otpResendCounter = 30; otpInterval: any; phoneOtpInfo!: OtpInfo; email = ''; onEmailVerifyResendStatus: 'CAN_RESEND' | 'CANNOT_RESEND' = 'CANNOT_RESEND'; onEmailVerifyResendStatus: 'APPLY_OTP' | 'CAN_RESEND' = 'APPLY_OTP'; emailResendCounter = 30; emailResendInterval: any; emailOtpInfo!: OtpInfo; registerDialogVisable = false; name = ''; agreeControct = false; isReadContract = false; phoneSuccessConfirmVisable = false; emailOtpConfirmVisable = false; registerDialogVisable = false; registerSuccessConfirmVisable = false; detectContructReadStatus(event: any): void { this.isReadContract = Math.round(event.target.scrollTop) === (event.target.scrollHeight - event.target.clientHeight); const scrollTop = Math.round(event.target.scrollTop); const height = event.target.scrollHeight - event.target.clientHeight; if (Math.floor(scrollTop/10) === (Math.floor(height/10))) { this.isReadContract = true; } }; get showPhoneOtpCodeField(): boolean { @@ -255,29 +336,141 @@ }; get showEmailVerifyField(): boolean { return this.connectDevice === 'EMAIL'; return this.connectDevice === 'EMAIL' && this.onEmailVerifyResendStatus !== 'APPLY_OTP'; }; applyOtpVerification(): void { this.onPhoneVerifyStep = 'INPUT_OTP'; get phoneValid() { const rule = /^09[0-9]{8}$/; return this.phoneNumber ? rule.test(this.phoneNumber) : true; } get emailValid() { const rule = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; return this.email ? rule.test(this.email) : true; } applyOtpVerification(type: string): void { const isMobile = this.connectDevice === 'MOBILE'; const loginInfo: LoginRequest = { loginType: isMobile ? 'SMS' : 'EMAIL', account: isMobile ? this.phoneNumber : this.email, } sendOtp(loginInfo).then(otpInfo => { if (otpInfo.success) { this.startOtpCount(type, otpInfo); } }); }; resentOtp(type: string) { if (type === 'MOBILE') { clearInterval(this.otpInterval); this.otpResendCounter = 30; this.otpCounter = '15:00'; this.startPhoneCounter(); } else { this.emailResendCounter = 30; this.startEmailCounter(); this.emailOtpConfirmVisable = true; } } private startOtpCount(type: string, otpInfo) { type === 'MOBILE' ? this.phoneOtpInfo = otpInfo : this.emailOtpInfo = otpInfo; if (type === 'MOBILE') { this.onPhoneVerifyStep = 'INPUT_OTP'; this.startPhoneCounter(); } else { this.onEmailVerifyResendStatus = 'CAN_RESEND'; this.startEmailCounter(); this.emailOtpConfirmVisable = true; } } private startEmailCounter() { this.emailResendInterval = setInterval(() => { this.emailResendCounter -= 1; if (this.emailResendCounter === 0) { clearInterval(this.emailResendInterval) } }, 1000) } private startPhoneCounter() { const minCount = this.otpCounter.split(':'); let secCount = (+minCount[0] * 60) + (+minCount[1]); let min = 0; let sec = 0; this.otpInterval = setInterval(() => { secCount -= 1; min = Math.floor(secCount/60); sec = Math.floor(secCount%60); this.otpCounter = `${min < 10 ? '0' + min : min}:${sec < 10 ? '0' + sec : sec}`; if (this.otpResendCounter !== 0) { this.otpResendCounter -= 1; } if (secCount === 0) { clearInterval(this.otpInterval) } }, 1000) } private setRegisterInfo(): RegisterInfo { return this.connectDevice === 'MOBILE' ? { phone: this.phoneNumber, indexKey: this.phoneOtpInfo.indexKey, otpCode: this.otpCode, name: this.name, contactType: 'SMS' } : { email: this.email, indexKey: this.emailOtpInfo.indexKey, otpCode: this.otpCode, name: this.name, contactType: 'EMAIL' } } applyAccount(): void { console.log('apply new account!') const registerInfo = this.setRegisterInfo(); register(registerInfo).then(res => { this.storageIdToken(res.data.id_token); this.storageRole(Role.USER); this.registerSuccessConfirmVisable = true; }) }; // TODO: å OTPèªèéç¼å æ«æä½¿ç¨ fakeLogin() { const user = { username: 'user', password: 'user', } login(user).then((res) => { this.storageIdToken(res.data.id_token); this.storageRole(Role.USER); this.$router.go(-1); }) }; confirmApplySuccess(): void { this.phoneSuccessConfirmVisable = false; this.registerSuccessConfirmVisable = false; this.$router.go(-1); } phoneLogin() { const login = { account: this.phoneNumber, indexKey: this.phoneOtpInfo.indexKey, otpCode: this.otpCode } loginVerify(login).then(res => { this.storageIdToken(res.data.id_token); this.storageRole(Role.USER); this.phoneSuccessConfirmVisable = true; }).catch(error => { if (error.response.status === 401) { this.registerDialogVisable = true; } }) } destroyed() { clearInterval(this.otpInterval); clearInterval(this.emailResendInterval); } } </script> @@ -299,8 +492,8 @@ width: calc(100% - 36px); border-radius: 10px !important; padding: 12px 18px !important; border-width: 1px; outline: none; border:1px solid #CCCCCC; outline: 0; @extend .text--middle; &::placeholder { color: $PRUDENTIAL_GREY; @@ -308,19 +501,6 @@ &.is-invalid { border: 1px solid $PRIMARY_RED !important; border-radius: 20px; } } .pam-otp-resend-btn { background: transparent; border: none; color: $PRIMARY_RED; font-weight: bold; i { margin-right: 10px; } &.disabled { color: $LIGHT_GREY; } } @@ -376,4 +556,18 @@ } } .pam-field-title__hint { @extend .smTxt_bold; color: #68737A; } .error { @extend .smTxt_bold; @extend .text--primary; height: 16px; } .pam-popUp-title { line-height: 24px; } </style> PAMapp/pages/myAppointmentList.vue
@@ -45,7 +45,6 @@ this.storeMyAppointmentList(); if (this.$route.name) { console.log('mounted route') this.activeTabName = this.$route.name.split('-')[1] } } PAMapp/store/localStorage.ts
@@ -2,8 +2,8 @@ @Module export default class LocalStorage extends VuexModule { id_token:string|null = null; role_State:string|null = null; id_token = localStorage.getItem('id_token'); role_State= localStorage.getItem('current_role'); get idToken(): string|null { return this.id_token; pamapi/src/doc/µù¥UAPI/µù¥UAPI.txt
¤ñ¹ï·sÀÉ®× @@ -0,0 +1,13 @@ post : http://localhost:8080/api/otp/register request body: { "phone": "0973000003", "email":"email@pollex.com.tw", "indexKey": "3485a742", "otpCode": "123", "name":"Jack", "contactType":"SMS" // "SMS"ï¼Otpç¼éææ©ï¼"EMAIL":Otpç¼email } pamapi/src/main/java/com/pollex/pam/domain/Customer.java
@@ -5,6 +5,8 @@ import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @@ -14,6 +16,7 @@ import org.springframework.data.annotation.LastModifiedDate; import com.fasterxml.jackson.annotation.JsonIgnore; import com.pollex.pam.enums.OtpLoginTypeEnum; @Entity @Table(name = "customer") @@ -37,6 +40,10 @@ @Column(name = "email") private String email; @Enumerated(value = EnumType.STRING) @Column(name = "contact_type") private OtpLoginTypeEnum contactType; @CreatedDate @Column(name = "created_date", updatable = false) @@ -95,6 +102,14 @@ public void setLastModifiedDate(Instant lastModifiedDate) { this.lastModifiedDate = lastModifiedDate; } public OtpLoginTypeEnum getContactType() { return contactType; } public void setContactType(OtpLoginTypeEnum contactType) { this.contactType = contactType; } pamapi/src/main/java/com/pollex/pam/domain/OtpTmp.java
¤ñ¹ï·sÀÉ®× @@ -0,0 +1,92 @@ package com.pollex.pam.domain; import java.io.Serializable; import java.time.Instant; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; import org.springframework.data.annotation.CreatedDate; import com.fasterxml.jackson.annotation.JsonIgnore; import com.pollex.pam.enums.OtpLoginTypeEnum; import com.pollex.pam.enums.OtpTmpStatusEnum; @Entity @Table(name = "otp_tmp") public class OtpTmp implements Serializable{ /** * */ private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "account") private String account; @Column(name = "index_key") private String indexKey; @Enumerated(value = EnumType.STRING) @Column(name = "status") private OtpTmpStatusEnum status; @CreatedDate @Column(name = "created_date", updatable = false) @JsonIgnore private Instant createdDate = Instant.now(); public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getIndexKey() { return indexKey; } public void setIndexKey(String indexKey) { this.indexKey = indexKey; } public OtpTmpStatusEnum getStatus() { return status; } public void setStatus(OtpTmpStatusEnum status) { this.status = status; } public Instant getCreatedDate() { return createdDate; } public void setCreatedDate(Instant createdDate) { this.createdDate = createdDate; } public String getAccount() { return account; } public void setAccount(String account) { this.account = account; } } pamapi/src/main/java/com/pollex/pam/enums/CustomerDetailEnum.java
@@ -3,7 +3,8 @@ public enum CustomerDetailEnum { ID("CustomerId"), NAME("CustomerName"), ACCOUNT("CustomerAccount"); ACCOUNT("CustomerAccount"), CONTACT_TYPE("ContactType"); private final String value; pamapi/src/main/java/com/pollex/pam/enums/OtpTmpStatusEnum.java
¤ñ¹ï·sÀÉ®× @@ -0,0 +1,6 @@ package com.pollex.pam.enums; public enum OtpTmpStatusEnum { UNVERIFIED, VERRIFIED } pamapi/src/main/java/com/pollex/pam/repository/OtpTmpRepository.java
¤ñ¹ï·sÀÉ®× @@ -0,0 +1,15 @@ package com.pollex.pam.repository; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import com.pollex.pam.domain.OtpTmp; @Repository public interface OtpTmpRepository extends JpaRepository<OtpTmp, Long>{ OtpTmp findByAccountAndIndexKey(String account, String indexKey); OtpTmp findByAccount(String account); } pamapi/src/main/java/com/pollex/pam/security/provider/OtpAuthenticationProvider.java
@@ -2,9 +2,13 @@ import com.pollex.pam.config.ApplicationProperties; import com.pollex.pam.domain.Customer; import com.pollex.pam.domain.OtpTmp; import com.pollex.pam.enums.CustomerDetailEnum; import com.pollex.pam.enums.OtpLoginTypeEnum; import com.pollex.pam.enums.OtpTmpStatusEnum; import com.pollex.pam.repository.CustomerRepository; import com.pollex.pam.security.token.OtpAuthenticationToken; import com.pollex.pam.service.OtpTmpService; import com.pollex.pam.service.OtpWebService; import com.pollex.pam.service.dto.OtpResponseDTO; import com.pollex.pam.web.rest.vm.OtpAccount; @@ -38,6 +42,9 @@ @Autowired CustomerRepository customerRepository; @Autowired OtpTmpService otpTmpService; public Authentication authenticate(OtpAuthenticationToken otpAuthenticationToken) throws AuthenticationException { OtpAccount otpAccount = otpAuthenticationToken.getPrincipal(); @@ -46,13 +53,15 @@ String otpCode = otpAuthenticationToken.getCredentials(); if(applicationProperty.isMockLogin()){ return getCustomerToken(account, otpCode); setVerrifiedOtpTmp(account, indexKey); return getCustomerToken(account, otpCode, indexKey); } try { OtpResponseDTO otpResponseDTO = otpWebService.verifyOTP(indexKey, otpCode); if(otpResponseDTO.isSuccess()) { return getCustomerToken(account, otpCode); setVerrifiedOtpTmp(account, indexKey); return getCustomerToken(account, otpCode, indexKey); } } catch (Exception e) { log.error("Exception: ", e); @@ -62,9 +71,18 @@ throw new AuthenticationCredentialsNotFoundException(""); } private UsernamePasswordAuthenticationToken getCustomerToken(String account, String otpCode) { // todo æªå卿¼DBæå±¬æ£å¸¸ç¾è±¡ï¼éç¨ç¹æ®messageåç¥å端å¯é²è¡è¨»å Customer customer = customerRepository.findOneByEmailEqualsOrPhoneEquals(account, account).orElseThrow(() -> new UsernameNotFoundException("this customer is not in db, account = " + account)); private void setVerrifiedOtpTmp(String account, String indexKey) { OtpTmp otpTmp = otpTmpService.findByAccountAndIndexKey(account, indexKey); otpTmp.setStatus(OtpTmpStatusEnum.VERRIFIED); otpTmpService.save(otpTmp); } private UsernamePasswordAuthenticationToken getCustomerToken(String account , String otpCode, String indexKey) { // todo æªå卿¼DBæå±¬æ£å¸¸ç¾è±¡ï¼éç¨ç¹æ®messageåç¥å端å¯é²è¡è¨»å Customer customer = customerRepository.findOneByEmailEqualsOrPhoneEquals(account, account).orElseThrow(() -> new UsernameNotFoundException("this customer is not in register, account = " + account)); List<GrantedAuthority> grantedAuths = Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(account, otpCode, grantedAuths); @@ -73,6 +91,7 @@ details.put(CustomerDetailEnum.ID.getValue(), customer.getId().toString()); details.put(CustomerDetailEnum.NAME.getValue(), customer.getName()); details.put(CustomerDetailEnum.ACCOUNT.getValue(), account); // details.put(CustomerDetailEnum.CONTACT_TYPE.getValue(), customer.getContactType()); authenticationToken.setDetails(details); return authenticationToken; pamapi/src/main/java/com/pollex/pam/service/CustomerAuthService.java
¤ñ¹ï·sÀÉ®× @@ -0,0 +1,34 @@ package com.pollex.pam.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import com.pollex.pam.security.jwt.TokenProvider; import com.pollex.pam.security.token.OtpAuthenticationToken; import com.pollex.pam.web.rest.vm.OtpAccount; @Service public class CustomerAuthService { @Autowired AuthenticationManagerBuilder authenticationManagerBuilder; @Autowired TokenProvider tokenProvider; public String authorize(String account, String indexKey, String otpCode) { OtpAccount otpAccount = new OtpAccount(account, indexKey); OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken( otpAccount, otpCode ); Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); SecurityContextHolder.getContext().setAuthentication(authenticationToken); String jwt = tokenProvider.createToken(authentication, false); return jwt; } } pamapi/src/main/java/com/pollex/pam/service/CustomerService.java
¤ñ¹ï·sÀÉ®× @@ -0,0 +1,47 @@ package com.pollex.pam.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import com.pollex.pam.domain.Customer; import com.pollex.pam.domain.OtpTmp; import com.pollex.pam.enums.OtpLoginTypeEnum; import com.pollex.pam.enums.OtpTmpStatusEnum; import com.pollex.pam.repository.CustomerRepository; import com.pollex.pam.service.dto.CustomerRegisterDTO; import com.pollex.pam.service.mapper.CustomerDTOMapper; @Service public class CustomerService { @Autowired CustomerRepository customerRepository; @Autowired CustomerDTOMapper customerDTOMapper; @Autowired CustomerAuthService customerAuthService; @Autowired OtpTmpService otpTmpService; public Customer save(Customer customer) { return customerRepository.save(customer); } public String registerCustomer(CustomerRegisterDTO registDTO) { String account = registDTO.getContactType() == OtpLoginTypeEnum.EMAIL?registDTO.getEmail():registDTO.getPhone(); OtpTmp otpTmp = otpTmpService.findByAccountAndIndexKey(account, registDTO.getIndexKey()); if(otpTmp.getStatus() == OtpTmpStatusEnum.VERRIFIED) { Customer customer = customerDTOMapper.toCustomer(registDTO); save(customer); String jwt = customerAuthService.authorize(account, registDTO.getIndexKey(), registDTO.getOtpCode()); return jwt; }else { throw new UsernameNotFoundException("Otp record not found"); } } } pamapi/src/main/java/com/pollex/pam/service/OtpTmpService.java
¤ñ¹ï·sÀÉ®× @@ -0,0 +1,39 @@ package com.pollex.pam.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.pollex.pam.domain.OtpTmp; import com.pollex.pam.enums.OtpLoginTypeEnum; import com.pollex.pam.enums.OtpTmpStatusEnum; import com.pollex.pam.repository.OtpTmpRepository; @Service public class OtpTmpService { @Autowired OtpTmpRepository otpTmpRepository; public OtpTmp createOtpTmp(String account, String indexKey) { OtpTmp oldTmp = otpTmpRepository.findByAccount(account); if(oldTmp==null) { OtpTmp otpTmp = new OtpTmp(); otpTmp.setIndexKey(indexKey); otpTmp.setAccount(account); otpTmp.setStatus(OtpTmpStatusEnum.UNVERIFIED); return otpTmpRepository.save(otpTmp); }else { oldTmp.setIndexKey(indexKey); oldTmp.setStatus(OtpTmpStatusEnum.UNVERIFIED); return otpTmpRepository.save(oldTmp); } } public OtpTmp findByAccountAndIndexKey(String account, String indexKey) { return otpTmpRepository.findByAccountAndIndexKey(account, indexKey); } public OtpTmp save(OtpTmp otpTmp) { return otpTmpRepository.save(otpTmp); } } pamapi/src/main/java/com/pollex/pam/service/UserService.java
@@ -1,18 +1,13 @@ package com.pollex.pam.service; import com.pollex.pam.config.Constants; import com.pollex.pam.domain.Authority; import com.pollex.pam.domain.User; import com.pollex.pam.repository.AuthorityRepository; import com.pollex.pam.repository.UserRepository; import com.pollex.pam.security.AuthoritiesConstants; import com.pollex.pam.security.SecurityUtils; import com.pollex.pam.service.dto.AdminUserDTO; import com.pollex.pam.service.dto.UserDTO; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.*; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cache.CacheManager; @@ -22,6 +17,16 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.pollex.pam.config.Constants; import com.pollex.pam.domain.Authority; import com.pollex.pam.domain.User; import com.pollex.pam.repository.AuthorityRepository; import com.pollex.pam.repository.UserRepository; import com.pollex.pam.security.SecurityUtils; import com.pollex.pam.service.dto.AdminUserDTO; import com.pollex.pam.service.dto.UserDTO; import tech.jhipster.security.RandomUtil; /** @@ -40,6 +45,7 @@ private final AuthorityRepository authorityRepository; private final CacheManager cacheManager; public UserService( UserRepository userRepository, @@ -93,47 +99,47 @@ }); } public User registerUser(AdminUserDTO userDTO, String password) { userRepository .findOneByLogin(userDTO.getLogin().toLowerCase()) .ifPresent(existingUser -> { boolean removed = removeNonActivatedUser(existingUser); if (!removed) { throw new UsernameAlreadyUsedException(); } }); userRepository .findOneByEmailIgnoreCase(userDTO.getEmail()) .ifPresent(existingUser -> { boolean removed = removeNonActivatedUser(existingUser); if (!removed) { throw new EmailAlreadyUsedException(); } }); User newUser = new User(); String encryptedPassword = passwordEncoder.encode(password); newUser.setLogin(userDTO.getLogin().toLowerCase()); // new user gets initially a generated password newUser.setPassword(encryptedPassword); newUser.setFirstName(userDTO.getFirstName()); newUser.setLastName(userDTO.getLastName()); if (userDTO.getEmail() != null) { newUser.setEmail(userDTO.getEmail().toLowerCase()); } newUser.setImageUrl(userDTO.getImageUrl()); newUser.setLangKey(userDTO.getLangKey()); // new user is not active newUser.setActivated(false); // new user gets registration key newUser.setActivationKey(RandomUtil.generateActivationKey()); Set<Authority> authorities = new HashSet<>(); authorityRepository.findById(AuthoritiesConstants.USER).ifPresent(authorities::add); newUser.setAuthorities(authorities); userRepository.save(newUser); this.clearUserCaches(newUser); log.debug("Created Information for User: {}", newUser); return newUser; } // public User registerUser(AdminUserDTO userDTO, String password) { // userRepository // .findOneByLogin(userDTO.getLogin().toLowerCase()) // .ifPresent(existingUser -> { // boolean removed = removeNonActivatedUser(existingUser); // if (!removed) { // throw new UsernameAlreadyUsedException(); // } // }); // userRepository // .findOneByEmailIgnoreCase(userDTO.getEmail()) // .ifPresent(existingUser -> { // boolean removed = removeNonActivatedUser(existingUser); // if (!removed) { // throw new EmailAlreadyUsedException(); // } // }); // User newUser = new User(); // String encryptedPassword = passwordEncoder.encode(password); // newUser.setLogin(userDTO.getLogin().toLowerCase()); // // new user gets initially a generated password // newUser.setPassword(encryptedPassword); // newUser.setFirstName(userDTO.getFirstName()); // newUser.setLastName(userDTO.getLastName()); // if (userDTO.getEmail() != null) { // newUser.setEmail(userDTO.getEmail().toLowerCase()); // } // newUser.setImageUrl(userDTO.getImageUrl()); // newUser.setLangKey(userDTO.getLangKey()); // // new user is not active // newUser.setActivated(false); // // new user gets registration key // newUser.setActivationKey(RandomUtil.generateActivationKey()); // Set<Authority> authorities = new HashSet<>(); // authorityRepository.findById(AuthoritiesConstants.USER).ifPresent(authorities::add); // newUser.setAuthorities(authorities); // userRepository.save(newUser); // this.clearUserCaches(newUser); // log.debug("Created Information for User: {}", newUser); // return newUser; // } private boolean removeNonActivatedUser(User existingUser) { if (existingUser.isActivated()) { @@ -322,4 +328,6 @@ Objects.requireNonNull(cacheManager.getCache(UserRepository.USERS_BY_EMAIL_CACHE)).evict(user.getEmail()); } } } pamapi/src/main/java/com/pollex/pam/service/dto/CustomerRegisterDTO.java
¤ñ¹ï·sÀÉ®× @@ -0,0 +1,53 @@ package com.pollex.pam.service.dto; import com.pollex.pam.enums.OtpLoginTypeEnum; public class CustomerRegisterDTO { private String name; private String phone; private String email; private OtpLoginTypeEnum contactType; private String indexKey; private String otpCode; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public OtpLoginTypeEnum getContactType() { return contactType; } public void setContactType(OtpLoginTypeEnum contactType) { this.contactType = contactType; } public String getIndexKey() { return indexKey; } public void setIndexKey(String indexKey) { this.indexKey = indexKey; } public String getOtpCode() { return otpCode; } public void setOtpCode(String otpCode) { this.otpCode = otpCode; } } pamapi/src/main/java/com/pollex/pam/service/mapper/CustomerDTOMapper.java
¤ñ¹ï·sÀÉ®× @@ -0,0 +1,17 @@ package com.pollex.pam.service.mapper; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import com.pollex.pam.domain.Customer; import com.pollex.pam.service.dto.CustomerRegisterDTO; @Service public class CustomerDTOMapper { public Customer toCustomer(CustomerRegisterDTO source) { Customer target = new Customer(); BeanUtils.copyProperties(source, target); return target; } } pamapi/src/main/java/com/pollex/pam/web/rest/AccountResource.java
@@ -3,10 +3,15 @@ import com.pollex.pam.domain.User; import com.pollex.pam.repository.UserRepository; import com.pollex.pam.security.SecurityUtils; import com.pollex.pam.security.jwt.JWTFilter; import com.pollex.pam.service.CustomerAuthService; import com.pollex.pam.service.CustomerService; import com.pollex.pam.service.MailService; import com.pollex.pam.service.UserService; import com.pollex.pam.service.dto.AdminUserDTO; import com.pollex.pam.service.dto.CustomerRegisterDTO; import com.pollex.pam.service.dto.PasswordChangeDTO; import com.pollex.pam.web.rest.UserJWTController.JWTToken; import com.pollex.pam.web.rest.errors.*; import com.pollex.pam.web.rest.vm.KeyAndPasswordVM; import com.pollex.pam.web.rest.vm.ManagedUserVM; @@ -16,7 +21,10 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; /** @@ -40,6 +48,9 @@ private final UserService userService; private final MailService mailService; @Autowired CustomerService customerService; public AccountResource(UserRepository userRepository, UserService userService, MailService mailService) { this.userRepository = userRepository; @@ -47,23 +58,25 @@ this.mailService = mailService; } /** * {@code POST /register} : register the user. * * @param managedUserVM the managed user View Model. * @throws InvalidPasswordException {@code 400 (Bad Request)} if the password is incorrect. * @throws EmailAlreadyUsedException {@code 400 (Bad Request)} if the email is already used. * @throws LoginAlreadyUsedException {@code 400 (Bad Request)} if the login is already used. */ @PostMapping("/register") @ResponseStatus(HttpStatus.CREATED) public void registerAccount(@Valid @RequestBody ManagedUserVM managedUserVM) { if (isPasswordLengthInvalid(managedUserVM.getPassword())) { throw new InvalidPasswordException(); } User user = userService.registerUser(managedUserVM, managedUserVM.getPassword()); mailService.sendActivationEmail(user); } // /** // * {@code POST /register} : register the user. // * // * @param managedUserVM the managed user View Model. // * @throws InvalidPasswordException {@code 400 (Bad Request)} if the password is incorrect. // * @throws EmailAlreadyUsedException {@code 400 (Bad Request)} if the email is already used. // * @throws LoginAlreadyUsedException {@code 400 (Bad Request)} if the login is already used. // */ // @PostMapping("/register") // @ResponseStatus(HttpStatus.CREATED) // public void registerAccount(@Valid @RequestBody ManagedUserVM managedUserVM) { // if (isPasswordLengthInvalid(managedUserVM.getPassword())) { // throw new InvalidPasswordException(); // } // User user = userService.registerUser(managedUserVM, managedUserVM.getPassword()); // mailService.sendActivationEmail(user); // } /** * {@code GET /activate} : activate the registered user. pamapi/src/main/java/com/pollex/pam/web/rest/OtpResource.java
@@ -5,7 +5,11 @@ import com.pollex.pam.security.jwt.JWTFilter; import com.pollex.pam.security.jwt.TokenProvider; import com.pollex.pam.security.token.OtpAuthenticationToken; import com.pollex.pam.service.CustomerAuthService; import com.pollex.pam.service.CustomerService; import com.pollex.pam.service.OtpTmpService; import com.pollex.pam.service.OtpWebService; import com.pollex.pam.service.dto.CustomerRegisterDTO; import com.pollex.pam.service.dto.OtpResponseDTO; import com.pollex.pam.web.rest.vm.*; import org.slf4j.Logger; @@ -41,15 +45,35 @@ @Autowired TokenProvider tokenProvider; @Autowired CustomerAuthService customerAuthService; @Autowired OtpTmpService otpTmpService; @Autowired CustomerService customerService; @PostMapping("/sendOtp") public ResponseEntity<Object> sendOtp(@RequestBody OtpLoginVM login) { if(applicationProperty.isMockLogin()) { return new ResponseEntity<>(getMockSendOtpResponse(), HttpStatus.OK); } if(login.getLoginType() == OtpLoginTypeEnum.SMS) { return new ResponseEntity<>(otpWebService.sendByPhone(login.getAccount()), HttpStatus.OK); OtpResponseDTO otpResponse; try { if(applicationProperty.isMockLogin()) { otpResponse = getMockSendOtpResponse(); }else if(login.getLoginType() == OtpLoginTypeEnum.SMS) { otpResponse = otpWebService.sendByPhone(login.getAccount()); } else if(login.getLoginType() == OtpLoginTypeEnum.EMAIL) { otpResponse = otpWebService.sendByEmail(login.getAccount()); }else { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("can not support this login type, loginType = " + login.getLoginType().name()); } otpTmpService.createOtpTmp(login.getAccount(), otpResponse.getIndexKey()); return new ResponseEntity<>(otpResponse, HttpStatus.OK); } catch (ServiceException | RemoteException e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("connecting otp web service error"); } else if(login.getLoginType() == OtpLoginTypeEnum.EMAIL) { return new ResponseEntity<>(otpWebService.sendByEmail(login.getAccount()), HttpStatus.OK); @@ -60,22 +84,22 @@ @PostMapping("/verify") public ResponseEntity<UserJWTController.JWTToken> verifyOtp(@RequestBody VerifyOtpVM verifyOtpParam) { OtpAccount otpAccount = new OtpAccount(verifyOtpParam.getAccount(), verifyOtpParam.getIndexKey()); OtpAuthenticationToken authenticationToken = new OtpAuthenticationToken( otpAccount, verifyOtpParam.getOtpCode() ); Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); SecurityContextHolder.getContext().setAuthentication(authenticationToken); String jwt = tokenProvider.createToken(authentication, false); String jwt = customerAuthService.authorize(verifyOtpParam.getAccount(), verifyOtpParam.getIndexKey(), verifyOtpParam.getOtpCode()); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer" + jwt); return new ResponseEntity<>(new UserJWTController.JWTToken(jwt), httpHeaders, HttpStatus.OK); } private OtpResponseDTO getMockSendOtpResponse() { String indexKey = UUID.randomUUID().toString().substring(0, 8); return new OtpResponseDTO(Arrays.asList(indexKey, "0", "", "")); } @PostMapping("/register") public ResponseEntity<UserJWTController.JWTToken> registerAccount(@RequestBody CustomerRegisterDTO registDTO) { String jwt = customerService.registerCustomer(registDTO); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer" + jwt); return new ResponseEntity<>(new UserJWTController.JWTToken(jwt), httpHeaders, HttpStatus.OK); } }