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 31d82e21..dc33fc29 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 @@ -5,6 +5,9 @@
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 bf2cc7d3..1a403a40 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 @@ -1,24 +1,18 @@ import { Component, OnInit, Inject, OnDestroy } from '@angular/core'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; -import { KEY_LOGIN_RES_INFO } from '@app/types/login-res-info.type'; -import { KEY_VER_INFO } from '@app/types/ver-info.type'; -import { SessionStorageService } from '@ucap-webmessenger/web-storage'; - -import { Store } from '@ngrx/store'; 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 {} +export interface ChangePasswordDialogData { + loginId: string; + phoneNumber?: string; + encryptedLoginPw?: string; +} export interface ChangePasswordDialogResult { choice: boolean; + currentLoginPw?: string; + newLoginPw?: string; } @Component({ @@ -27,170 +21,25 @@ export interface ChangePasswordDialogResult { styleUrls: ['./change-password.dialog.component.scss'] }) export class ChangePasswordDialogComponent implements OnInit, OnDestroy { - loginRes: LoginResponse; - sessionVerinfo: VersionInfo2Response; - isMe: boolean; - constructor( public dialogRef: MatDialogRef< ChangePasswordDialogData, ChangePasswordDialogResult >, @Inject(MAT_DIALOG_DATA) public data: ChangePasswordDialogData, - private dialogService: DialogService, - private sessionStorageService: SessionStorageService, - private appAuthenticationService: AppAuthenticationService, - private store: Store - ) { - this.sessionVerinfo = this.sessionStorageService.get( - KEY_VER_INFO - ); - this.loginRes = this.sessionStorageService.get( - KEY_LOGIN_RES_INFO - ); - } + private dialogService: DialogService + ) {} 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 }); + onChangePassword(info: { currentLoginPw: string; newLoginPw: string }): void { + this.dialogRef.close({ + choice: true, + currentLoginPw: info.currentLoginPw, + newLoginPw: info.newLoginPw + }); } onClickChoice(choice: boolean): void { 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 91d22349..65e38deb 100644 --- a/projects/ucap-webmessenger-app/src/app/services/authentication.service.ts +++ b/projects/ucap-webmessenger-app/src/app/services/authentication.service.ts @@ -1,7 +1,5 @@ import { Injectable } from '@angular/core'; -import CryptoJS from 'crypto-js'; - import { SessionStorageService, LocalStorageService @@ -10,6 +8,7 @@ import { LocaleCode } from '@ucap-webmessenger/core'; import { LoginInfo, KEY_LOGIN_INFO } from '../types'; import { KEY_VER_INFO } from '@app/types/ver-info.type'; import { KEY_LOGIN_RES_INFO } from '@app/types/login-res-info.type'; +import { PasswordUtil } from '@ucap-webmessenger/pi'; @Injectable({ providedIn: 'root' @@ -33,7 +32,7 @@ export class AppAuthenticationService { this.sessionStorageService.set(KEY_LOGIN_INFO, { ...loginInfo, initPw: loginInfo.loginId === loginInfo.loginPw, - loginPw: CryptoJS.enc.Hex.stringify(CryptoJS.SHA256(loginInfo.loginPw)) + loginPw: PasswordUtil.encrypt(loginInfo.loginPw) }); if (rememberMe) { @@ -51,11 +50,4 @@ 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 b61192f1..26c09e5c 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,3 +96,20 @@ export const privacyAgreeFailure = createAction( export const privacyDisagree = createAction( '[Account::Authentication] Privacy Disagree' ); + +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/account/authentication/effects.ts b/projects/ucap-webmessenger-app/src/app/store/account/authentication/effects.ts index e4fb861f..595f862a 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 @@ -34,7 +34,10 @@ import { privacyAgreeSuccess, increaseLoginFailCount, initialLoginFailCount, - logoutInitialize + logoutInitialize, + userPasswordSet, + userPasswordSetSuccess, + userPasswordSetFailure } from './actions'; import { LoginInfo, @@ -60,6 +63,10 @@ import { ChangePasswordDialogData, ChangePasswordDialogResult } from '@app/layouts/messenger/dialogs/account/change-password.dialog.component'; +import { + ServiceProtocolService, + UserPasswordSetResponse +} from '@ucap-webmessenger/protocol-service'; @Injectable() export class Effects { @@ -273,10 +280,24 @@ export class Effects { width: '500px', height: '500px', disableClose: false, - data: {} + data: { + loginId: loginInfo.loginId, + encryptedLoginPw: loginInfo.loginPw, + phoneNumber: loginRes.userInfo.hpNumber + } }); if (!!result && result.choice) { + this.store.dispatch( + userPasswordSet({ + req: { + companyCode: loginInfo.companyCode, + loginId: loginInfo.loginId, + oldLoginPw: result.currentLoginPw, + newLoginPw: result.newLoginPw + } + }) + ); } else { return; } @@ -324,6 +345,23 @@ 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 ngZone: NgZone, @@ -334,7 +372,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 3d4d543f..44ba4d69 100644 --- a/projects/ucap-webmessenger-app/src/app/store/messenger/index.ts +++ b/projects/ucap-webmessenger-app/src/app/store/messenger/index.ts @@ -6,7 +6,6 @@ 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'; @@ -17,7 +16,6 @@ export interface State { option: OptionStore.State; query: QueryStore.State; room: RoomStore.State; - service: ServiceStore.State; status: StatusStore.State; sync: SyncStore.State; settings: SettingsStore.State; @@ -29,7 +27,6 @@ export const effects: Type[] = [ OptionStore.Effects, QueryStore.Effects, RoomStore.Effects, - ServiceStore.Effects, StatusStore.Effects, SyncStore.Effects, SettingsStore.Effects @@ -42,7 +39,6 @@ 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 @@ -66,9 +62,6 @@ 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 deleted file mode 100644 index f6956adb..00000000 --- a/projects/ucap-webmessenger-app/src/app/store/messenger/service/actions.ts +++ /dev/null @@ -1,23 +0,0 @@ -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 deleted file mode 100644 index 175351cd..00000000 --- a/projects/ucap-webmessenger-app/src/app/store/messenger/service/effects.ts +++ /dev/null @@ -1,47 +0,0 @@ -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 deleted file mode 100644 index 2663cade..00000000 --- a/projects/ucap-webmessenger-app/src/app/store/messenger/service/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -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 deleted file mode 100644 index 70e7e209..00000000 --- a/projects/ucap-webmessenger-app/src/app/store/messenger/service/reducers.ts +++ /dev/null @@ -1,4 +0,0 @@ -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 deleted file mode 100644 index 1e05bd08..00000000 --- a/projects/ucap-webmessenger-app/src/app/store/messenger/service/state.ts +++ /dev/null @@ -1,9 +0,0 @@ -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-pi/src/lib/utils/password.util.ts b/projects/ucap-webmessenger-pi/src/lib/utils/password.util.ts new file mode 100644 index 00000000..e95d24b2 --- /dev/null +++ b/projects/ucap-webmessenger-pi/src/lib/utils/password.util.ts @@ -0,0 +1,137 @@ +import CryptoJS from 'crypto-js'; +import { StringUtil, CharactorType } from '@ucap-webmessenger/core'; + +export interface PasswordValidationOption { + userId?: string; + phoneNumber?: string; +} + +export enum PasswordValidationResult { + Valid = 'Valid', + Empty = 'Empty', + IncludeUserId = 'IncludeUserId', + IncludePhoneNumber = 'IncludePhoneNumber', + IncludeSpace = 'IncludeSpace', + IncludeRepeated = 'IncludeRepeated', + IncludeIncrements = 'IncludeIncrements', + IncludeDecrements = 'IncludeDecrements', + LackOfCombination = 'LackOfCombination', + TooShortCombinationLength = 'TooShortCombinationLength' +} + +export class PasswordUtil { + static encrypt(password: string): string { + return CryptoJS.enc.Hex.stringify(CryptoJS.SHA256(password)); + } + + static validate( + password: string, + option?: PasswordValidationOption + ): { code: PasswordValidationResult; extra?: any } { + if (!password || '' === password.trim()) { + return { + code: PasswordValidationResult.Empty + }; + } + + if (!!option && !!option.userId) { + if (-1 < password.indexOf(option.userId)) { + return { + code: PasswordValidationResult.IncludeUserId + }; + } + } + + if (!!option && !!option.phoneNumber) { + const phoneNumberOnly = option.phoneNumber + .trim() + .replace(new RegExp('-', 'g'), ''); + + switch (phoneNumberOnly.length) { + case 11: + if (-1 < password.indexOf(phoneNumberOnly.substr(3, 4))) { + return { + code: PasswordValidationResult.IncludePhoneNumber + }; + } + if (-1 < password.indexOf(phoneNumberOnly.substr(7, 4))) { + return { + code: PasswordValidationResult.IncludePhoneNumber + }; + } + break; + case 10: + if (-1 < password.indexOf(phoneNumberOnly.substr(3, 3))) { + return { + code: PasswordValidationResult.IncludePhoneNumber + }; + } + if (-1 < password.indexOf(phoneNumberOnly.substr(6, 4))) { + return { + code: PasswordValidationResult.IncludePhoneNumber + }; + } + break; + default: + break; + } + } + + if (0 < StringUtil.includes(password, CharactorType.Space)) { + return { + code: PasswordValidationResult.IncludeSpace + }; + } + if (StringUtil.isRepeat(password, 3)) { + return { + code: PasswordValidationResult.IncludeRepeated + }; + } + if (StringUtil.isIncrements(password, 3)) { + return { + code: PasswordValidationResult.IncludeIncrements + }; + } + if (StringUtil.isDecrements(password, 3)) { + return { + code: PasswordValidationResult.IncludeDecrements + }; + } + + let combinationCount = 0; + if (0 < StringUtil.includes(password, CharactorType.Special)) { + combinationCount++; + } + if (0 < StringUtil.includes(password, CharactorType.Number)) { + combinationCount++; + } + if (0 < StringUtil.includes(password, CharactorType.Charactor)) { + combinationCount++; + } + + let minPwLen = 0; + if (2 < combinationCount) { + minPwLen = 8; + } else if (2 === combinationCount) { + minPwLen = 10; + } else if (2 > combinationCount) { + return { + code: PasswordValidationResult.LackOfCombination + }; + } + + if (password.length < minPwLen) { + return { + code: PasswordValidationResult.TooShortCombinationLength, + extra: { + combinationCount, + minPwLen + } + }; + } + + return { + code: PasswordValidationResult.Valid + }; + } +} diff --git a/projects/ucap-webmessenger-pi/src/public-api.ts b/projects/ucap-webmessenger-pi/src/public-api.ts index 4fee4a46..6bb87dda 100644 --- a/projects/ucap-webmessenger-pi/src/public-api.ts +++ b/projects/ucap-webmessenger-pi/src/public-api.ts @@ -12,6 +12,8 @@ export * from './lib/types/response-status.type'; export * from './lib/services/pi.service'; +export * from './lib/utils/password.util'; + export * from './lib/ucap-pi.module'; export * from './lib/config/urls'; 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 61744c94..47c1ca22 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 @@ -5,43 +5,137 @@ - - 현재 패스워드를 입력해 주세요 - 신규 패스워드 - - - 신규 패스워드를 입력해 주세요 - + 신규 패스워드 확인 - - 신규 패스워드 확인을 입력해 주세요 - +
+ + 현재 비밀번호를 입력해 주세요 + + + 현재 비밀번호와 일치하지 않습니다 + + + + 신규 비밀번호를 입력해 주세요 + + + 현재 비밀번호와 동일합니다 + + + + + 비밀번호에는 공백을 입력할 수 없습니다 + + + 사용자 ID를 비밀번호에 포함할 수 없습니다 + + + 사용자 휴대폰번호를 비밀번호에 포함할 수 없습니다 + + + 숫자나 문자를 3번이상 반복적으로 사용할 수 없습니다 + + + 연속되는 숫자나 문자를 3번이상 사용할 수 없습니다 + + + 연속되는 숫자나 문자를 3번이상 사용할 수 없습니다 + + + 2종류 이상 조합을 해야 합니다 + + + 비밀번호에는 공백을 입력할 수 없습니다 + + + 비밀번호는{{ + newLoginPwFormControl.getError('ucapPassword').result.extra + .combinationCount + }}가지가 조합된 경우{{ + newLoginPwFormControl.getError('ucapPassword').result.extra + .minPwLen + }}자를 넘어야 합니다 + + + + + 신규 비밀번호 확인을 입력해 주세요 + + + 신규 비밀번호와 신규 비밀번호 확인이 다릅니다 + +
+