change password is modified

This commit is contained in:
병준 박 2019-11-29 14:26:59 +09:00
parent 68e861a39b
commit 31153b52bf
15 changed files with 443 additions and 318 deletions

View File

@ -5,6 +5,9 @@
<mat-card-content> <mat-card-content>
<div fxFlex class="setting-tab"> <div fxFlex class="setting-tab">
<ucap-account-change-password <ucap-account-change-password
[loginId]="data.loginId"
[phoneNumber]="data.phoneNumber"
[encryptedLoginPw]="data.encryptedLoginPw"
(changePassword)="onChangePassword($event)" (changePassword)="onChangePassword($event)"
></ucap-account-change-password> ></ucap-account-change-password>
</div> </div>

View File

@ -1,24 +1,18 @@
import { Component, OnInit, Inject, OnDestroy } from '@angular/core'; import { Component, OnInit, Inject, OnDestroy } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; 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 { 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'; export interface ChangePasswordDialogData {
import CryptoJS from 'crypto-js'; loginId: string;
phoneNumber?: string;
export interface ChangePasswordDialogData {} encryptedLoginPw?: string;
}
export interface ChangePasswordDialogResult { export interface ChangePasswordDialogResult {
choice: boolean; choice: boolean;
currentLoginPw?: string;
newLoginPw?: string;
} }
@Component({ @Component({
@ -27,170 +21,25 @@ export interface ChangePasswordDialogResult {
styleUrls: ['./change-password.dialog.component.scss'] styleUrls: ['./change-password.dialog.component.scss']
}) })
export class ChangePasswordDialogComponent implements OnInit, OnDestroy { export class ChangePasswordDialogComponent implements OnInit, OnDestroy {
loginRes: LoginResponse;
sessionVerinfo: VersionInfo2Response;
isMe: boolean;
constructor( constructor(
public dialogRef: MatDialogRef< public dialogRef: MatDialogRef<
ChangePasswordDialogData, ChangePasswordDialogData,
ChangePasswordDialogResult ChangePasswordDialogResult
>, >,
@Inject(MAT_DIALOG_DATA) public data: ChangePasswordDialogData, @Inject(MAT_DIALOG_DATA) public data: ChangePasswordDialogData,
private dialogService: DialogService, private dialogService: DialogService
private sessionStorageService: SessionStorageService, ) {}
private appAuthenticationService: AppAuthenticationService,
private store: Store<any>
) {
this.sessionVerinfo = this.sessionStorageService.get<VersionInfo2Response>(
KEY_VER_INFO
);
this.loginRes = this.sessionStorageService.get<LoginResponse>(
KEY_LOGIN_RES_INFO
);
}
ngOnInit() {} ngOnInit() {}
ngOnDestroy(): void {} ngOnDestroy(): void {}
onChangePassword(info: { onChangePassword(info: { currentLoginPw: string; newLoginPw: string }): void {
currentLoginPw: string; this.dialogRef.close({
newLoginPw: string; choice: true,
newConfirmLoginPw: string; currentLoginPw: info.currentLoginPw,
notValid: () => void; newLoginPw: info.newLoginPw
}): 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 { onClickChoice(choice: boolean): void {

View File

@ -1,7 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import CryptoJS from 'crypto-js';
import { import {
SessionStorageService, SessionStorageService,
LocalStorageService LocalStorageService
@ -10,6 +8,7 @@ import { LocaleCode } from '@ucap-webmessenger/core';
import { LoginInfo, KEY_LOGIN_INFO } from '../types'; import { LoginInfo, KEY_LOGIN_INFO } from '../types';
import { KEY_VER_INFO } from '@app/types/ver-info.type'; import { KEY_VER_INFO } from '@app/types/ver-info.type';
import { KEY_LOGIN_RES_INFO } from '@app/types/login-res-info.type'; import { KEY_LOGIN_RES_INFO } from '@app/types/login-res-info.type';
import { PasswordUtil } from '@ucap-webmessenger/pi';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -33,7 +32,7 @@ export class AppAuthenticationService {
this.sessionStorageService.set<LoginInfo>(KEY_LOGIN_INFO, { this.sessionStorageService.set<LoginInfo>(KEY_LOGIN_INFO, {
...loginInfo, ...loginInfo,
initPw: loginInfo.loginId === loginInfo.loginPw, initPw: loginInfo.loginId === loginInfo.loginPw,
loginPw: CryptoJS.enc.Hex.stringify(CryptoJS.SHA256(loginInfo.loginPw)) loginPw: PasswordUtil.encrypt(loginInfo.loginPw)
}); });
if (rememberMe) { if (rememberMe) {
@ -51,11 +50,4 @@ export class AppAuthenticationService {
this.sessionStorageService.remove(KEY_VER_INFO); this.sessionStorageService.remove(KEY_VER_INFO);
this.sessionStorageService.remove(KEY_LOGIN_INFO); this.sessionStorageService.remove(KEY_LOGIN_INFO);
} }
isSameForPassword(pw: string): boolean {
const loginInfo = this.sessionStorageService.get<LoginInfo>(KEY_LOGIN_INFO);
return (
loginInfo.loginPw === CryptoJS.enc.Hex.stringify(CryptoJS.SHA256(pw))
);
}
} }

View File

@ -96,3 +96,20 @@ export const privacyAgreeFailure = createAction(
export const privacyDisagree = createAction( export const privacyDisagree = createAction(
'[Account::Authentication] Privacy Disagree' '[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 }>()
);

View File

@ -34,7 +34,10 @@ import {
privacyAgreeSuccess, privacyAgreeSuccess,
increaseLoginFailCount, increaseLoginFailCount,
initialLoginFailCount, initialLoginFailCount,
logoutInitialize logoutInitialize,
userPasswordSet,
userPasswordSetSuccess,
userPasswordSetFailure
} from './actions'; } from './actions';
import { import {
LoginInfo, LoginInfo,
@ -60,6 +63,10 @@ import {
ChangePasswordDialogData, ChangePasswordDialogData,
ChangePasswordDialogResult ChangePasswordDialogResult
} from '@app/layouts/messenger/dialogs/account/change-password.dialog.component'; } from '@app/layouts/messenger/dialogs/account/change-password.dialog.component';
import {
ServiceProtocolService,
UserPasswordSetResponse
} from '@ucap-webmessenger/protocol-service';
@Injectable() @Injectable()
export class Effects { export class Effects {
@ -273,10 +280,24 @@ export class Effects {
width: '500px', width: '500px',
height: '500px', height: '500px',
disableClose: false, disableClose: false,
data: {} data: {
loginId: loginInfo.loginId,
encryptedLoginPw: loginInfo.loginPw,
phoneNumber: loginRes.userInfo.hpNumber
}
}); });
if (!!result && result.choice) { if (!!result && result.choice) {
this.store.dispatch(
userPasswordSet({
req: {
companyCode: loginInfo.companyCode,
loginId: loginInfo.loginId,
oldLoginPw: result.currentLoginPw,
newLoginPw: result.newLoginPw
}
})
);
} else { } else {
return; 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( constructor(
private actions$: Actions, private actions$: Actions,
private ngZone: NgZone, private ngZone: NgZone,
@ -334,7 +372,7 @@ export class Effects {
private appAuthenticationService: AppAuthenticationService, private appAuthenticationService: AppAuthenticationService,
private protocolService: ProtocolService, private protocolService: ProtocolService,
private authenticationProtocolService: AuthenticationProtocolService, private authenticationProtocolService: AuthenticationProtocolService,
private serviceProtocolService: ServiceProtocolService,
private dialogService: DialogService, private dialogService: DialogService,
@Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService, @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService,
private logger: NGXLogger private logger: NGXLogger

View File

@ -6,7 +6,6 @@ import * as EventStore from './event';
import * as OptionStore from './option'; import * as OptionStore from './option';
import * as QueryStore from './query'; import * as QueryStore from './query';
import * as RoomStore from './room'; import * as RoomStore from './room';
import * as ServiceStore from './service';
import * as StatusStore from './status'; import * as StatusStore from './status';
import * as SyncStore from './sync'; import * as SyncStore from './sync';
import * as SettingsStore from './settings'; import * as SettingsStore from './settings';
@ -17,7 +16,6 @@ export interface State {
option: OptionStore.State; option: OptionStore.State;
query: QueryStore.State; query: QueryStore.State;
room: RoomStore.State; room: RoomStore.State;
service: ServiceStore.State;
status: StatusStore.State; status: StatusStore.State;
sync: SyncStore.State; sync: SyncStore.State;
settings: SettingsStore.State; settings: SettingsStore.State;
@ -29,7 +27,6 @@ export const effects: Type<any>[] = [
OptionStore.Effects, OptionStore.Effects,
QueryStore.Effects, QueryStore.Effects,
RoomStore.Effects, RoomStore.Effects,
ServiceStore.Effects,
StatusStore.Effects, StatusStore.Effects,
SyncStore.Effects, SyncStore.Effects,
SettingsStore.Effects SettingsStore.Effects
@ -42,7 +39,6 @@ export function reducers(state: State | undefined, action: Action) {
option: OptionStore.reducer, option: OptionStore.reducer,
query: QueryStore.reducer, query: QueryStore.reducer,
room: RoomStore.reducer, room: RoomStore.reducer,
service: ServiceStore.reducer,
status: StatusStore.reducer, status: StatusStore.reducer,
sync: SyncStore.reducer, sync: SyncStore.reducer,
settings: SettingsStore.reducer settings: SettingsStore.reducer
@ -66,9 +62,6 @@ export function selectors<S>(selector: Selector<any, State>) {
QuerySelector: QueryStore.selectors( QuerySelector: QueryStore.selectors(
createSelector(selector, (state: State) => state.query) createSelector(selector, (state: State) => state.query)
), ),
ServiceSelector: ServiceStore.selectors(
createSelector(selector, (state: State) => state.service)
),
StatusSelector: StatusStore.selectors( StatusSelector: StatusStore.selectors(
createSelector(selector, (state: State) => state.status) createSelector(selector, (state: State) => state.status)
), ),

View File

@ -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 }>()
);

View File

@ -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<any>,
private logger: NGXLogger
) {}
}

View File

@ -1,4 +0,0 @@
export * from './actions';
export * from './effects';
export * from './reducers';
export * from './state';

View File

@ -1,4 +0,0 @@
import { createReducer, on } from '@ngrx/store';
import { initialState } from './state';
export const reducer = createReducer(initialState);

View File

@ -1,9 +0,0 @@
import { Selector, createSelector } from '@ngrx/store';
export interface State {}
export const initialState: State = {};
export function selectors<S>(selector: Selector<any, State>) {
return {};
}

View File

@ -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
};
}
}

View File

@ -12,6 +12,8 @@ export * from './lib/types/response-status.type';
export * from './lib/services/pi.service'; export * from './lib/services/pi.service';
export * from './lib/utils/password.util';
export * from './lib/ucap-pi.module'; export * from './lib/ucap-pi.module';
export * from './lib/config/urls'; export * from './lib/config/urls';

View File

@ -5,43 +5,137 @@
<input <input
matInput matInput
type="password" type="password"
formControlName="currentLoginPw" [formControl]="currentLoginPwFormControl"
#currentLoginPw
/> />
<mat-error>
현재 패스워드를 입력해 주세요
</mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<mat-label>신규 패스워드</mat-label> <mat-label>신규 패스워드</mat-label>
<input <input matInput type="password" [formControl]="newLoginPwFormControl" />
matInput
type="password"
formControlName="newLoginPw"
#newLoginPw
/>
<mat-error>
신규 패스워드를 입력해 주세요
</mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<mat-label>신규 패스워드 확인</mat-label> <mat-label>신규 패스워드 확인</mat-label>
<input <input
matInput matInput
type="password" type="password"
formControlName="newConfirmLoginPw" [formControl]="newConfirmLoginPwFormControl"
#newConfirmLoginPw
/> />
<mat-error>
신규 패스워드 확인을 입력해 주세요
</mat-error>
</mat-form-field> </mat-form-field>
<div>
<mat-error
*ngIf="
currentLoginPwFormControl.dirty &&
currentLoginPwFormControl.hasError('required')
"
>
현재 비밀번호를 입력해 주세요
</mat-error>
<mat-error
*ngIf="
currentLoginPwFormControl.dirty &&
currentLoginPwFormControl.hasError('ucapPasswordSame')
"
>
현재 비밀번호와 일치하지 않습니다
</mat-error>
<mat-error
*ngIf="
newLoginPwFormControl.dirty &&
newLoginPwFormControl.hasError('required')
"
>
신규 비밀번호를 입력해 주세요
</mat-error>
<mat-error
*ngIf="
newLoginPwFormControl.dirty &&
newLoginPwFormControl.hasError('ucapNotSameWith')
"
>
현재 비밀번호와 동일합니다
</mat-error>
<mat-error
*ngIf="
newLoginPwFormControl.dirty &&
newLoginPwFormControl.hasError('ucapPassword')
"
>
<ng-container
[ngSwitch]="
newLoginPwFormControl.getError('ucapPassword').result.code
"
>
<ng-container *ngSwitchCase="PasswordValidationResult.IncludeSpace">
비밀번호에는 공백을 입력할 수 없습니다
</ng-container>
<ng-container *ngSwitchCase="PasswordValidationResult.IncludeUserId">
사용자 ID를 비밀번호에 포함할 수 없습니다
</ng-container>
<ng-container
*ngSwitchCase="PasswordValidationResult.IncludePhoneNumber"
>
사용자 휴대폰번호를 비밀번호에 포함할 수 없습니다
</ng-container>
<ng-container
*ngSwitchCase="PasswordValidationResult.IncludeRepeated"
>
숫자나 문자를 3번이상 반복적으로 사용할 수 없습니다
</ng-container>
<ng-container
*ngSwitchCase="PasswordValidationResult.IncludeIncrements"
>
연속되는 숫자나 문자를 3번이상 사용할 수 없습니다
</ng-container>
<ng-container
*ngSwitchCase="PasswordValidationResult.IncludeDecrements"
>
연속되는 숫자나 문자를 3번이상 사용할 수 없습니다
</ng-container>
<ng-container
*ngSwitchCase="PasswordValidationResult.LackOfCombination"
>
2종류 이상 조합을 해야 합니다
</ng-container>
<ng-container
*ngSwitchCase="PasswordValidationResult.TooShortCombinationLength"
>
비밀번호에는 공백을 입력할 수 없습니다
</ng-container>
<ng-container *ngSwitchCase="PasswordValidationResult.IncludeSpace">
비밀번호는{{
newLoginPwFormControl.getError('ucapPassword').result.extra
.combinationCount
}}가지가 조합된 경우{{
newLoginPwFormControl.getError('ucapPassword').result.extra
.minPwLen
}}자를 넘어야 합니다
</ng-container>
</ng-container>
</mat-error>
<mat-error
*ngIf="
newConfirmLoginPwFormControl.dirty &&
newConfirmLoginPwFormControl.hasError('required')
"
>
신규 비밀번호 확인을 입력해 주세요
</mat-error>
<mat-error
*ngIf="
newConfirmLoginPwFormControl.dirty &&
newConfirmLoginPwFormControl.hasError('ucapSameWith')
"
>
신규 비밀번호와 신규 비밀번호 확인이 다릅니다
</mat-error>
</div>
<button <button
mat-raised-button mat-raised-button
class="submit-button bg-accent-dark" class="submit-button bg-accent-dark"
aria-label="패스워드 변경" aria-label="패스워드 변경"
[disabled]="changePasswordForm.invalid || !changePasswordBtnEnable" [disabled]="changePasswordForm.invalid"
(click)="onClickChangePassword()" (click)="onClickChangePassword()"
> >
패스워드 변경 패스워드 변경

View File

@ -8,10 +8,66 @@ import {
ElementRef, ElementRef,
ChangeDetectorRef ChangeDetectorRef
} from '@angular/core'; } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import {
import { Company } from '@ucap-webmessenger/api-external'; FormControl,
import { LocalStorageService } from '@ucap-webmessenger/web-storage'; FormGroup,
import { LoginInfo, KEY_LOGIN_INFO } from '@app/types'; Validators,
ValidatorFn,
AbstractControl,
ValidationErrors,
FormBuilder
} from '@angular/forms';
import {
PasswordUtil,
PasswordValidationOption,
PasswordValidationResult
} from '@ucap-webmessenger/pi';
export function ucapPassword(option: PasswordValidationOption): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const result = PasswordUtil.validate(control.value, option);
return PasswordValidationResult.Valid !== result.code
? { ucapPassword: { result } }
: null;
};
}
export function ucapPasswordSame(value: string): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
return !!value && value !== PasswordUtil.encrypt(control.value)
? { ucapPasswordSame: true }
: null;
};
}
export function ucapNotSameWith(targetControl: AbstractControl): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
return !!targetControl &&
(targetControl.value as string) === (control.value as string)
? { ucapNotSameWith: true }
: null;
};
}
export function ucapSameWith(targetControl: AbstractControl): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
return !!targetControl && targetControl.value !== control.value
? { ucapSameWith: true }
: null;
};
}
export function ucapNotSame(value: string | number): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
return !!value && value === control.value ? { ucapNotSame: true } : null;
};
}
export function ucapSame(value: string | number): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
return !!value && value !== control.value ? { ucapSame: true } : null;
};
}
@Component({ @Component({
selector: 'ucap-account-change-password', selector: 'ucap-account-change-password',
@ -20,48 +76,79 @@ import { LoginInfo, KEY_LOGIN_INFO } from '@app/types';
}) })
export class ChangePasswordComponent implements OnInit { export class ChangePasswordComponent implements OnInit {
@Input() @Input()
changePasswordBtnEnable: boolean; loginId: string;
@Input()
phoneNumber: string;
@Input()
encryptedLoginPw: string;
@Output() @Output()
changePassword = new EventEmitter<{ changePassword = new EventEmitter<{
currentLoginPw: string; currentLoginPw: string;
newLoginPw: string; newLoginPw: string;
newConfirmLoginPw: string;
notValid: () => void;
}>(); }>();
@ViewChild('currentLoginPw', { static: true })
currentLoginPwElementRef: ElementRef;
@ViewChild('newLoginPw', { static: true })
newLoginPwElementRef: ElementRef;
changePasswordForm: FormGroup; changePasswordForm: FormGroup;
currentLoginPwFormControl = new FormControl('');
newLoginPwFormControl = new FormControl('');
newConfirmLoginPwFormControl = new FormControl('');
PasswordValidationResult = PasswordValidationResult;
constructor( constructor(
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef
private localStorageService: LocalStorageService
) {} ) {}
ngOnInit() { ngOnInit() {
const loginInfo: LoginInfo = this.localStorageService.get<LoginInfo>( const currentLoginPwValidators: ValidatorFn[] = [Validators.required];
KEY_LOGIN_INFO if (!!this.encryptedLoginPw) {
currentLoginPwValidators.push(ucapPasswordSame(this.encryptedLoginPw));
}
this.currentLoginPwFormControl.setValidators(currentLoginPwValidators);
let validateOption: PasswordValidationOption = {};
if (!!this.loginId) {
validateOption = {
...validateOption,
userId: this.loginId
};
}
if (!!this.phoneNumber) {
validateOption = {
...validateOption,
phoneNumber: this.phoneNumber
};
}
const newLoginPwValidators: ValidatorFn[] = [
Validators.required,
ucapNotSameWith(this.currentLoginPwFormControl),
ucapPassword(validateOption)
];
this.newLoginPwFormControl.setValidators(newLoginPwValidators);
const newConfirmLoginPwValidators: ValidatorFn[] = [
Validators.required,
ucapSameWith(this.newLoginPwFormControl)
];
this.newConfirmLoginPwFormControl.setValidators(
newConfirmLoginPwValidators
); );
this.changePasswordForm = this.formBuilder.group({ this.changePasswordForm = this.formBuilder.group({
currentLoginPw: ['', Validators.required], currentLoginPwFormControl: this.currentLoginPwFormControl,
newLoginPw: ['', Validators.required], newLoginPwFormControl: this.newLoginPwFormControl,
newConfirmLoginPw: ['', Validators.required] newConfirmLoginPwFormControl: this.newConfirmLoginPwFormControl
}); });
} }
onClickChangePassword() { onClickChangePassword() {
this.changePassword.emit({ this.changePassword.emit({
currentLoginPw: this.changePasswordForm.get('currentLoginPw').value, currentLoginPw: this.currentLoginPwFormControl.value,
newLoginPw: this.changePasswordForm.get('newLoginPw').value, newLoginPw: this.newLoginPwFormControl.value
newConfirmLoginPw: this.changePasswordForm.get('newConfirmLoginPw').value,
notValid: () => {
this.currentLoginPwElementRef.nativeElement.focus();
}
}); });
} }
} }