diff --git a/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/account/change-password.dialog.component.html b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/account/change-password.dialog.component.html index ab2a14fe..31d82e21 100644 --- a/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/account/change-password.dialog.component.html +++ b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/account/change-password.dialog.component.html @@ -1 +1,21 @@ - + + + 패스워드 변경 + + +
+ +
+
+ + + +
diff --git a/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/account/change-password.dialog.component.scss b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/account/change-password.dialog.component.scss index e69de29b..eeffa9a1 100644 --- a/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/account/change-password.dialog.component.scss +++ b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/account/change-password.dialog.component.scss @@ -0,0 +1,35 @@ +::ng-deep .setting-frame { + padding: 16px; + height: 100%; + min-width: 400px; + position: relative; + + .mat-dialog-container { + position: relative; + } + + .mat-card-header { + position: relative; + width: 100%; + border-bottom: 1px solid #dddddd; + margin-bottom: 12px; + } + + .mat-card-content { + flex: 0 0 auto; + display: flex; + align-items: flex-start; + height: calc(100% - 100px); + border-bottom: 1px solid #dddddd; + } + + .button-farm { + text-align: right; + position: absolute; + width: 100%; + bottom: 10px; + .mat-primary { + margin-left: 4px; + } + } +} diff --git a/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/account/change-password.dialog.component.ts b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/account/change-password.dialog.component.ts index 7cf9d439..bf2cc7d3 100644 --- a/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/account/change-password.dialog.component.ts +++ b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/account/change-password.dialog.component.ts @@ -6,15 +6,14 @@ import { SessionStorageService } from '@ucap-webmessenger/web-storage'; import { Store } from '@ngrx/store'; -import { UserInfo } from '@ucap-webmessenger/protocol-sync'; -import { - UserInfoSS, - UserInfoF, - UserInfoDN -} from '@ucap-webmessenger/protocol-query'; import { DialogService } from '@ucap-webmessenger/ui'; import { VersionInfo2Response } from '@ucap-webmessenger/api-public'; import { LoginResponse } from '@ucap-webmessenger/protocol-authentication'; +import { AppAuthenticationService } from '@app/services/authentication.service'; +import { StringUtil, CharactorType } from '@ucap-webmessenger/core'; + +import * as ServiceStore from '@app/store/messenger/service'; +import CryptoJS from 'crypto-js'; export interface ChangePasswordDialogData {} @@ -40,6 +39,7 @@ export class ChangePasswordDialogComponent implements OnInit, OnDestroy { @Inject(MAT_DIALOG_DATA) public data: ChangePasswordDialogData, private dialogService: DialogService, private sessionStorageService: SessionStorageService, + private appAuthenticationService: AppAuthenticationService, private store: Store ) { this.sessionVerinfo = this.sessionStorageService.get( @@ -53,4 +53,147 @@ export class ChangePasswordDialogComponent implements OnInit, OnDestroy { ngOnInit() {} ngOnDestroy(): void {} + + onChangePassword(info: { + currentLoginPw: string; + newLoginPw: string; + newConfirmLoginPw: string; + notValid: () => void; + }): void { + if (!info.currentLoginPw || '' === info.currentLoginPw.trim()) { + // 현재 비밀번호를 입력해주세요 + info.notValid(); + return; + } + if (!this.appAuthenticationService.isSameForPassword(info.currentLoginPw)) { + // "현재 비밀번호가 다릅니다" + info.notValid(); + return; + } + if (!info.newLoginPw || '' === info.newLoginPw.trim()) { + // 신규 비밀번호를 입력해주세요 + info.notValid(); + return; + } + if (-1 < info.newLoginPw.indexOf(this.loginRes.userId)) { + // "사용자 ID를 비밀번호에 포함할 수 없습니다." + info.notValid(); + return; + } + const phoneNumberOnly = !!this.loginRes.userInfo.hpNumber + ? this.loginRes.userInfo.hpNumber.trim().replace(new RegExp('-', 'g'), '') + : ''; + + switch (phoneNumberOnly.length) { + case 11: + if (-1 < info.newLoginPw.indexOf(phoneNumberOnly.substr(3, 4))) { + // "사용자 휴대폰번호를 비밀번호에 포함할 수 없습니다." + info.notValid(); + return; + } + if (-1 < info.newLoginPw.indexOf(phoneNumberOnly.substr(7, 4))) { + // "사용자 휴대폰번호를 비밀번호에 포함할 수 없습니다." + info.notValid(); + return; + } + break; + case 10: + if (-1 < info.newLoginPw.indexOf(phoneNumberOnly.substr(3, 3))) { + // "사용자 휴대폰번호를 비밀번호에 포함할 수 없습니다." + info.notValid(); + return; + } + if (-1 < info.newLoginPw.indexOf(phoneNumberOnly.substr(6, 4))) { + // "사용자 휴대폰번호를 비밀번호에 포함할 수 없습니다." + info.notValid(); + return; + } + break; + + default: + break; + } + + if (!info.newConfirmLoginPw || '' === info.newConfirmLoginPw.trim()) { + // "신규 비밀번호 확인을 입력해주세요" + info.notValid(); + return; + } + if (info.newLoginPw !== info.newConfirmLoginPw.trim()) { + // "신규 비밀번호와 신규 비밀번호 확인이 다릅니다" + info.notValid(); + return; + } + if (info.currentLoginPw === info.newLoginPw.trim()) { + // "현재 비밀번호와 새로운 비밀번호가 같습니다." + info.notValid(); + return; + } + if (0 < StringUtil.includes(info.newLoginPw, CharactorType.Space)) { + // "비밀번호에는 공백을 입력할 수 없습니다." + info.notValid(); + return; + } + if (StringUtil.isRepeat(info.newLoginPw, 3)) { + // "숫자나 문자를 3번이상 반복적으로 사용할 수 없습니다." + info.notValid(); + return; + } + if ( + StringUtil.isIncrements(info.newLoginPw, 3) || + StringUtil.isDecrements(info.newLoginPw, 3) + ) { + // "연속되는 숫자나 문자를 3번이상 사용할 수 없습니다." + info.notValid(); + return; + } + + let combinationCount = 0; + if (0 < StringUtil.includes(info.newLoginPw, CharactorType.Special)) { + combinationCount++; + } + if (0 < StringUtil.includes(info.newLoginPw, CharactorType.Number)) { + combinationCount++; + } + if (0 < StringUtil.includes(info.newLoginPw, CharactorType.Charactor)) { + combinationCount++; + } + + let minPwLen = 0; + if (2 < combinationCount) { + minPwLen = 8; + } else if (2 === combinationCount) { + minPwLen = 10; + } else if (2 > combinationCount) { + // "2종류 이상 조합을 해야 합니다." + info.notValid(); + return; + } + + if (info.newLoginPw.length < minPwLen) { + // "비밀번호는 %d가지가 조합된 경우 %d자를 넘어야 합니다." + info.notValid(); + return; + } + + this.store.dispatch( + ServiceStore.userPasswordSet({ + req: { + loginId: this.loginRes.userId, + companyCode: this.loginRes.companyCode, + oldLoginPw: CryptoJS.enc.Hex.stringify( + CryptoJS.SHA256(info.currentLoginPw) + ), + newLoginPw: CryptoJS.enc.Hex.stringify( + CryptoJS.SHA256(info.newLoginPw) + ) + } + }) + ); + this.dialogRef.close({ choice: true }); + } + + onClickChoice(choice: boolean): void { + this.dialogRef.close({ choice }); + } } diff --git a/projects/ucap-webmessenger-app/src/app/services/authentication.service.ts b/projects/ucap-webmessenger-app/src/app/services/authentication.service.ts index 346ef739..91d22349 100644 --- a/projects/ucap-webmessenger-app/src/app/services/authentication.service.ts +++ b/projects/ucap-webmessenger-app/src/app/services/authentication.service.ts @@ -51,4 +51,11 @@ export class AppAuthenticationService { this.sessionStorageService.remove(KEY_VER_INFO); this.sessionStorageService.remove(KEY_LOGIN_INFO); } + + isSameForPassword(pw: string): boolean { + const loginInfo = this.sessionStorageService.get(KEY_LOGIN_INFO); + return ( + loginInfo.loginPw === CryptoJS.enc.Hex.stringify(CryptoJS.SHA256(pw)) + ); + } } diff --git a/projects/ucap-webmessenger-app/src/app/store/account/authentication/actions.ts b/projects/ucap-webmessenger-app/src/app/store/account/authentication/actions.ts index ec4aa37f..b61192f1 100644 --- a/projects/ucap-webmessenger-app/src/app/store/account/authentication/actions.ts +++ b/projects/ucap-webmessenger-app/src/app/store/account/authentication/actions.ts @@ -96,20 +96,3 @@ export const privacyAgreeFailure = createAction( export const privacyDisagree = createAction( '[Account::Authentication] Privacy Disagree' ); - -export const changePassword = createAction( - '[Account::Authentication] Change Password', - props<{ req: UserPasswordSetRequest }>() -); - -export const changePasswordSuccess = createAction( - '[Account::Authentication] Change Password Success', - props<{ - res: UserPasswordSetResponse; - }>() -); - -export const changePasswordFailure = createAction( - '[Account::Authentication] Change Password Failure', - props<{ error: any }>() -); diff --git a/projects/ucap-webmessenger-app/src/app/store/account/authentication/effects.ts b/projects/ucap-webmessenger-app/src/app/store/account/authentication/effects.ts index 5636e48f..e4fb861f 100644 --- a/projects/ucap-webmessenger-app/src/app/store/account/authentication/effects.ts +++ b/projects/ucap-webmessenger-app/src/app/store/account/authentication/effects.ts @@ -32,9 +32,6 @@ import { privacyDisagree, privacyAgreeFailure, privacyAgreeSuccess, - changePassword, - changePasswordFailure, - changePasswordSuccess, increaseLoginFailCount, initialLoginFailCount, logoutInitialize @@ -49,10 +46,7 @@ import { AppAuthenticationService } from '@app/services/authentication.service'; import { NGXLogger } from 'ngx-logger'; import { Store } from '@ngrx/store'; import { SessionStorageService } from '@ucap-webmessenger/web-storage'; -import { - ServiceProtocolService, - UserPasswordSetResponse -} from '@ucap-webmessenger/protocol-service'; + import { AuthenticationProtocolService, LoginResponse @@ -330,23 +324,6 @@ export class Effects { ) ); - changePassword$ = createEffect(() => - this.actions$.pipe( - ofType(changePassword), - map(action => action.req), - exhaustMap(req => - this.serviceProtocolService.userPasswordSet(req).pipe( - map((res: UserPasswordSetResponse) => { - return changePasswordSuccess({ - res - }); - }), - catchError(error => of(changePasswordFailure({ error }))) - ) - ) - ) - ); - constructor( private actions$: Actions, private ngZone: NgZone, @@ -357,7 +334,7 @@ export class Effects { private appAuthenticationService: AppAuthenticationService, private protocolService: ProtocolService, private authenticationProtocolService: AuthenticationProtocolService, - private serviceProtocolService: ServiceProtocolService, + private dialogService: DialogService, @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService, private logger: NGXLogger diff --git a/projects/ucap-webmessenger-app/src/app/store/messenger/index.ts b/projects/ucap-webmessenger-app/src/app/store/messenger/index.ts index 44ba4d69..3d4d543f 100644 --- a/projects/ucap-webmessenger-app/src/app/store/messenger/index.ts +++ b/projects/ucap-webmessenger-app/src/app/store/messenger/index.ts @@ -6,6 +6,7 @@ import * as EventStore from './event'; import * as OptionStore from './option'; import * as QueryStore from './query'; import * as RoomStore from './room'; +import * as ServiceStore from './service'; import * as StatusStore from './status'; import * as SyncStore from './sync'; import * as SettingsStore from './settings'; @@ -16,6 +17,7 @@ export interface State { option: OptionStore.State; query: QueryStore.State; room: RoomStore.State; + service: ServiceStore.State; status: StatusStore.State; sync: SyncStore.State; settings: SettingsStore.State; @@ -27,6 +29,7 @@ export const effects: Type[] = [ OptionStore.Effects, QueryStore.Effects, RoomStore.Effects, + ServiceStore.Effects, StatusStore.Effects, SyncStore.Effects, SettingsStore.Effects @@ -39,6 +42,7 @@ export function reducers(state: State | undefined, action: Action) { option: OptionStore.reducer, query: QueryStore.reducer, room: RoomStore.reducer, + service: ServiceStore.reducer, status: StatusStore.reducer, sync: SyncStore.reducer, settings: SettingsStore.reducer @@ -62,6 +66,9 @@ export function selectors(selector: Selector) { QuerySelector: QueryStore.selectors( createSelector(selector, (state: State) => state.query) ), + ServiceSelector: ServiceStore.selectors( + createSelector(selector, (state: State) => state.service) + ), StatusSelector: StatusStore.selectors( createSelector(selector, (state: State) => state.status) ), diff --git a/projects/ucap-webmessenger-app/src/app/store/messenger/service/actions.ts b/projects/ucap-webmessenger-app/src/app/store/messenger/service/actions.ts new file mode 100644 index 00000000..f6956adb --- /dev/null +++ b/projects/ucap-webmessenger-app/src/app/store/messenger/service/actions.ts @@ -0,0 +1,23 @@ +import { createAction, props } from '@ngrx/store'; + +import { + UserPasswordSetRequest, + UserPasswordSetResponse +} from '@ucap-webmessenger/protocol-service'; + +export const userPasswordSet = createAction( + '[Account::Authentication] User Password Set', + props<{ req: UserPasswordSetRequest }>() +); + +export const userPasswordSetSuccess = createAction( + '[Account::Authentication] User Password Set Success', + props<{ + res: UserPasswordSetResponse; + }>() +); + +export const userPasswordSetFailure = createAction( + '[Account::Authentication] User Password Set Failure', + props<{ error: any }>() +); diff --git a/projects/ucap-webmessenger-app/src/app/store/messenger/service/effects.ts b/projects/ucap-webmessenger-app/src/app/store/messenger/service/effects.ts new file mode 100644 index 00000000..175351cd --- /dev/null +++ b/projects/ucap-webmessenger-app/src/app/store/messenger/service/effects.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@angular/core'; + +import { Actions, ofType, createEffect } from '@ngrx/effects'; + +import { Store } from '@ngrx/store'; + +import { NGXLogger } from 'ngx-logger'; +import { catchError, exhaustMap, map } from 'rxjs/operators'; + +import { of } from 'rxjs'; + +import { + userPasswordSet, + userPasswordSetSuccess, + userPasswordSetFailure +} from './actions'; +import { + ServiceProtocolService, + UserPasswordSetResponse +} from '@ucap-webmessenger/protocol-service'; + +@Injectable() +export class Effects { + userPasswordSet$ = createEffect(() => + this.actions$.pipe( + ofType(userPasswordSet), + map(action => action.req), + exhaustMap(req => + this.serviceProtocolService.userPasswordSet(req).pipe( + map((res: UserPasswordSetResponse) => { + return userPasswordSetSuccess({ + res + }); + }), + catchError(error => of(userPasswordSetFailure({ error }))) + ) + ) + ) + ); + + constructor( + private actions$: Actions, + private serviceProtocolService: ServiceProtocolService, + private store: Store, + private logger: NGXLogger + ) {} +} diff --git a/projects/ucap-webmessenger-app/src/app/store/messenger/service/index.ts b/projects/ucap-webmessenger-app/src/app/store/messenger/service/index.ts new file mode 100644 index 00000000..2663cade --- /dev/null +++ b/projects/ucap-webmessenger-app/src/app/store/messenger/service/index.ts @@ -0,0 +1,4 @@ +export * from './actions'; +export * from './effects'; +export * from './reducers'; +export * from './state'; diff --git a/projects/ucap-webmessenger-app/src/app/store/messenger/service/reducers.ts b/projects/ucap-webmessenger-app/src/app/store/messenger/service/reducers.ts new file mode 100644 index 00000000..70e7e209 --- /dev/null +++ b/projects/ucap-webmessenger-app/src/app/store/messenger/service/reducers.ts @@ -0,0 +1,4 @@ +import { createReducer, on } from '@ngrx/store'; +import { initialState } from './state'; + +export const reducer = createReducer(initialState); diff --git a/projects/ucap-webmessenger-app/src/app/store/messenger/service/state.ts b/projects/ucap-webmessenger-app/src/app/store/messenger/service/state.ts new file mode 100644 index 00000000..1e05bd08 --- /dev/null +++ b/projects/ucap-webmessenger-app/src/app/store/messenger/service/state.ts @@ -0,0 +1,9 @@ +import { Selector, createSelector } from '@ngrx/store'; + +export interface State {} + +export const initialState: State = {}; + +export function selectors(selector: Selector) { + return {}; +} diff --git a/projects/ucap-webmessenger-core/src/lib/utils/string.util.ts b/projects/ucap-webmessenger-core/src/lib/utils/string.util.ts index ffd83d2d..f8f68136 100644 --- a/projects/ucap-webmessenger-core/src/lib/utils/string.util.ts +++ b/projects/ucap-webmessenger-core/src/lib/utils/string.util.ts @@ -1,3 +1,13 @@ +export enum CharactorType { + Number = 'Number', + Charactor = 'Charactor', + Space = 'Space', + Special = 'Special' +} + +const CharCodeZero = '0'.charCodeAt(0); +const CharCodeNine = '9'.charCodeAt(0); + export class StringUtil { /** * prefix zero fill @@ -15,4 +25,160 @@ export class StringUtil { return StringUtil.zeroFill(str.toString(), len); } } + + /** + * includes + */ + public static includes(target: string, charactorType: CharactorType): number { + let nNum = 0; + let nChar = 0; + let nSpace = 0; + let nElse = 0; + + for (let i = 0; i < target.length; i++) { + const char = target.charCodeAt(i); + // 숫자 + if (char >= 48 && char <= 57) { + nNum++; + } else if (char >= 65 && char <= 90) { + // 영어(대문자) + nChar++; + } else if (char >= 97 && char <= 122) { + // 영어(소문자) + nChar++; + } else if (target.charAt(i) === ' ') { + // 공백 + nSpace++; + } else { + // 특수기호 + nElse++; + } + } + + switch (charactorType) { + case CharactorType.Number: + return nNum; + case CharactorType.Charactor: + return nChar; + case CharactorType.Space: + return nSpace; + case CharactorType.Special: + return nElse; + + default: + return -1; + } + } + + /** + * isRepeat + */ + public static isRepeat(target: string, repeatTime: number) { + let old = ''; + let rptCnt = 1; + for (let i = 0; i < target.length; i++) { + const s = target.charAt(i); + if (old === s) { + rptCnt++; + if (rptCnt === repeatTime) { + return true; + } + } else { + old = s; + rptCnt = 1; + } + } + return false; + } + + /** + * isIncrements + */ + public static isIncrements(target: string, repeatTime: number) { + for (let i = 0; i < target.length - (repeatTime - 1); i++) { + let notNeed = false; + const ary: number[] = []; + for (let j = 0; j < repeatTime; j++) { + const char = target.charCodeAt(i + j); + if (!StringUtil.isAlphaNumeric(char)) { + notNeed = true; + break; + } + ary.push(char); + } + + if (notNeed) { + continue; + } + + for (let k = 1; k < ary.length; k++) { + if (ary[0] !== ary[k] - k) { + notNeed = true; + break; + } + } + + if (notNeed) { + continue; + } + + return true; + } + return false; + } + + /** + * isIncrements + */ + public static isDecrements(target: string, repeatTime: number) { + for (let i = 0; i < target.length - (repeatTime - 1); i++) { + let notNeed = false; + const ary: number[] = []; + for (let j = 0; j < repeatTime; j++) { + const char = target.charCodeAt(i + j); + if (!StringUtil.isAlphaNumeric(char)) { + notNeed = true; + break; + } + ary.push(char); + } + + if (notNeed) { + continue; + } + + for (let k = 1; k < ary.length; k++) { + if (ary[0] !== ary[k] + k) { + notNeed = true; + break; + } + } + + if (notNeed) { + continue; + } + + return true; + } + return false; + } + + /** + * isAlphabet + */ + public static isAlphabet(char: number): boolean { + return (char >= 65 && char <= 90) || (char >= 97 && char <= 122); + } + /** + * isNumeric + */ + public static isNumeric(char: number): boolean { + return char >= 48 && char <= 57; + } + /** + * isAlphaNumeric + */ + public static isAlphaNumeric(char: number): boolean { + return StringUtil.isAlphabet(char) || StringUtil.isNumeric(char); + } } diff --git a/projects/ucap-webmessenger-ui-account/src/lib/components/change-password.component.html b/projects/ucap-webmessenger-ui-account/src/lib/components/change-password.component.html index d1be60c8..61744c94 100644 --- a/projects/ucap-webmessenger-ui-account/src/lib/components/change-password.component.html +++ b/projects/ucap-webmessenger-ui-account/src/lib/components/change-password.component.html @@ -1,6 +1,4 @@
-
비밀번호 변경
-
현재 패스워드 @@ -44,7 +42,7 @@ class="submit-button bg-accent-dark" aria-label="패스워드 변경" [disabled]="changePasswordForm.invalid || !changePasswordBtnEnable" - (click)="onClickLogin()" + (click)="onClickChangePassword()" > 패스워드 변경 diff --git a/projects/ucap-webmessenger-ui-account/src/lib/components/change-password.component.scss b/projects/ucap-webmessenger-ui-account/src/lib/components/change-password.component.scss index 5b6661e2..9d9a7f92 100644 --- a/projects/ucap-webmessenger-ui-account/src/lib/components/change-password.component.scss +++ b/projects/ucap-webmessenger-ui-account/src/lib/components/change-password.component.scss @@ -1,12 +1,9 @@ .change-password-form { position: relative; - width: 384px; - max-width: 384px; - padding: 50px; + width: 100%; + min-width: 380px; + padding: 0px; text-align: center; - background-color: rgba(255, 255, 255, 0.8); - border-radius: 0px; - box-shadow: 4px 4px 0px rgba(0, 0, 0, 0.1); .mat-title { margin: 16px 0 32px 0; diff --git a/projects/ucap-webmessenger-ui-account/src/lib/components/change-password.component.ts b/projects/ucap-webmessenger-ui-account/src/lib/components/change-password.component.ts index babf3638..c560adb3 100644 --- a/projects/ucap-webmessenger-ui-account/src/lib/components/change-password.component.ts +++ b/projects/ucap-webmessenger-ui-account/src/lib/components/change-password.component.ts @@ -54,7 +54,7 @@ export class ChangePasswordComponent implements OnInit { }); } - onClickLogin() { + onClickChangePassword() { this.changePassword.emit({ currentLoginPw: this.changePasswordForm.get('currentLoginPw').value, newLoginPw: this.changePasswordForm.get('newLoginPw').value,