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/_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,6 +1,7 @@ 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) { @@ -76,13 +77,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 +87,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, PAMapp/assets/ts/api/share.ts
@@ -1,31 +1,47 @@ 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(); 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
@@ -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> 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
@@ -4,7 +4,7 @@ <div class="pam-paragraph"> <div class="mdTxt"> ç¶å®æ¹å¼ é©èæ¹å¼<small class="pam-field-title__hint pl-10">(é¡§åæä»¥æ¨æå®çæ¹å¼èæ¨è¯ç¹«)</small> </div> <div class="pam-tags"> <el-row type="flex" class="pt-10"> @@ -15,7 +15,6 @@ :class="{ 'active': connectDevice === 'EMAIL'}" @click="connectDevice = 'EMAIL'">Email</el-button> </el-row> </div> <el-row type="flex" class="pt-10" v-show="connectDevice === 'MOBILE'"> @@ -213,7 +212,20 @@ </span> </el-dialog> <PopUpFrame class="pam-popUpFrame" :isOpen.sync="applySuccessConfirmVisable"> <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> </div> @@ -223,12 +235,14 @@ 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 { 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'; @@ -242,6 +256,7 @@ emailResendCounter = 30; registerDialogVisable = false; applySuccessConfirmVisable = false; name = ''; agreeControct = false; isReadContract = false; @@ -263,9 +278,14 @@ }; applyAccount(): void { this.applySuccessConfirmVisable = true; console.log('apply new account!') }; confirmApplySuccess(): void { this.applySuccessConfirmVisable = false } // TODO: å OTPèªèéç¼å æ«æä½¿ç¨ fakeLogin() { const user = { 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/main/java/com/pollex/pam/security/provider/EServiceAuthenticationProvider.java
@@ -17,6 +17,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.*; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -32,10 +34,7 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.*; @Component public class EServiceAuthenticationProvider { @@ -98,6 +97,7 @@ String dtoJson = new ObjectMapper().writeValueAsString(dto); RestTemplate restTemplate = getTrustAllRestTemplate(); settingMessageConvertesToSpecifyType(restTemplate, MediaType.ALL); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); @@ -120,4 +120,12 @@ requestFactory.setReadTimeout(300000); return new RestTemplate(requestFactory); } 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); } } pamapi/src/main/java/com/pollex/pam/web/rest/TestLoginResource.java
@@ -1,5 +1,6 @@ package com.pollex.pam.web.rest; import com.fasterxml.jackson.databind.ObjectMapper; import com.pollex.pam.config.ApplicationProperties; import com.pollex.pam.security.jwt.JWTFilter; import com.pollex.pam.security.jwt.TokenProvider; @@ -7,22 +8,39 @@ import com.pollex.pam.security.token.OtpAuthenticationToken; import com.pollex.pam.service.LoginService; import com.pollex.pam.service.OtpWebService; import com.pollex.pam.service.dto.EServiceRequest; import com.pollex.pam.service.dto.EServiceResponse; import com.pollex.pam.service.dto.OtpResponseDTO; import com.pollex.pam.web.rest.vm.OtpAccount; import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.ssl.SSLContexts; 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.http.*; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 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.web.bind.annotation.*; import org.springframework.web.client.RestTemplate; import tw.com.softleader.otp.ws.OtpWebServicePortBindingStub; import javax.net.ssl.SSLContext; import javax.xml.rpc.ServiceException; import java.rmi.RemoteException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; import java.util.List; // todoï¼å çºåææ¥loginæ¹ä¾¿ä½¿ç¨èç¨getçæ¹å¼ç»å ¥ï¼ç®åå·²æåºOtpResourceèEServiceResourceï¼ä¸»è¦æ¯ç¨éå ©ååç»å ¥ @@ -73,17 +91,46 @@ } @GetMapping("/byEService") public ResponseEntity<UserJWTController.JWTToken> loginByEService(@RequestParam("account") String account, @RequestParam("password") String password) throws Exception { EServiceAuthenticationToken authenticationToken = new EServiceAuthenticationToken( account, password ); public ResponseEntity<EServiceResponse> loginByEService(@RequestParam("account") String account, @RequestParam("password") String password) throws Exception { EServiceRequest dto = new EServiceRequest(); dto.setFunc("ValidateUserLogin"); dto.setId(account); dto.setPin(password); dto.setPwd(password); dto.setSys("epos"); Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); SecurityContextHolder.getContext().setAuthentication(authenticationToken); String jwt = tokenProvider.createToken(authentication, false); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.add(JWTFilter.AUTHORIZATION_HEADER, "Bearer" + jwt); return new ResponseEntity<>(new UserJWTController.JWTToken(jwt), httpHeaders, HttpStatus.OK); String dtoJson = new ObjectMapper().writeValueAsString(dto); RestTemplate restTemplate = getTrustAllRestTemplate(); settingMessageConvertesToSpecifyType(restTemplate, MediaType.ALL); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity<String> entity = new HttpEntity<>(dtoJson, headers); return restTemplate.exchange(applicationProperty.geteServiceLoginUrl(), HttpMethod.POST, 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); } } pamapi/src/main/resources/config/application-sit.yml
@@ -115,4 +115,4 @@ otp-web-service-url: https://vtwlifeopensyssit.pru.intranet.asia:443/pcalife-otp/ws/otpWebService otp-web-service-password: es20!%Pass otp-web-service-system-type: epos e-service-login-url: https://ssotwsit.eservice.pcalife.com.tw/sso/acctValidate e-service-login-url: https://eserviceuat.pcalife.com.tw/sso/chatbotValidate pamapi/src/main/resources/logback-spring.xml
@@ -4,6 +4,27 @@ <configuration scan="true"> <include resource="org/springframework/boot/logging/logback/base.xml"/> <property name="logback.dir" value="/appublic/applications/pamapi"/> <springProfile name="sit,uat,prod"> <appender name="logToFile" class="ch.qos.logback.core.rolling.RollingFileAppender"> <File>${logback.dir}/pamapi_server.log</File> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <FileNamePattern>${logback.dir}/log.%d{yyyy-MM-dd}.zip</FileNamePattern> <maxHistory>180</maxHistory> <totalSizeCap>2GB</totalSizeCap> </rollingPolicy> <encoder> <charset>UTF-8</charset> <pattern>%d [%thread] %-5level %logger{36} %line - %msg%n</pattern> </encoder> </appender> <root level="DEBUG"> <appender-ref ref="logToFile" /> </root> </springProfile> <!-- The FILE and ASYNC appenders are here as examples for a production configuration --> <!-- <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">