diff --git a/projects/ucap-webmessenger-api-common/src/lib/apis/file-profile-save.ts b/projects/ucap-webmessenger-api-common/src/lib/apis/file-profile-save.ts index 22ef07f0..9d42a1b2 100644 --- a/projects/ucap-webmessenger-api-common/src/lib/apis/file-profile-save.ts +++ b/projects/ucap-webmessenger-api-common/src/lib/apis/file-profile-save.ts @@ -2,35 +2,67 @@ import { DeviceType } from '@ucap-webmessenger/core'; import { APIRequest, APIResponse, - APIEncoder, APIDecoder, - ParameterUtil + ParameterUtil, + APIFormDataEncoder, + JsonAnalization, + StatusCode } from '@ucap-webmessenger/api'; +import { FileUploadItem } from '../models/file-upload-item'; export interface FileProfileSaveRequest extends APIRequest { userSeq: number; deviceType: DeviceType; token: string; file?: File; + fileUploadItem: FileUploadItem; intro?: string; initProfileImage?: boolean; } export interface FileProfileSaveResponse extends APIResponse { - ProfileURL?: string; - ProfileSubDir?: string; + profileURL?: string; + profileSubDir?: string; + returnJson?: any; } -const fileProfileEncodeMap = {}; +const fileProfileEncodeMap = { + userSeq: 'p_user_seq', + deviceType: 'p_device_type', + token: 'p_token', + file: 'file', + intro: 'p_intro', + initProfileImage: 'p_init_profile_img_yn' +}; -export const encodeFileProfileSave: APIEncoder = ( +export const encodeFileProfileSave: APIFormDataEncoder = ( req: FileProfileSaveRequest ) => { - return ParameterUtil.encode(fileProfileEncodeMap, req); + const extraParams: any = {}; + + extraParams.userSeq = String(req.userSeq); + if (!!req.initProfileImage) { + extraParams.initProfileImage = req.initProfileImage ? 'Y' : 'N'; + } + + return ParameterUtil.encodeFormData(fileProfileEncodeMap, req, extraParams); }; export const decodeFileProfileSave: APIDecoder = ( res: any ) => { - return {} as FileProfileSaveResponse; + try { + const json = JsonAnalization.receiveAnalization(res); + return { + statusCode: json.StatusCode, + profileURL: json.ProfileURL, + profileSubDir: json.ProfileSubDir, + returnJson: res + } as FileProfileSaveResponse; + } catch (e) { + return { + statusCode: StatusCode.Fail, + errorMessage: e + } as FileProfileSaveResponse; + } }; diff --git a/projects/ucap-webmessenger-api-common/src/lib/services/common-api.service.ts b/projects/ucap-webmessenger-api-common/src/lib/services/common-api.service.ts index 53c6a91b..79db4fef 100644 --- a/projects/ucap-webmessenger-api-common/src/lib/services/common-api.service.ts +++ b/projects/ucap-webmessenger-api-common/src/lib/services/common-api.service.ts @@ -94,15 +94,29 @@ export class CommonApiService { req: FileProfileSaveRequest, fileProfileSaveUrl?: string ): Observable { - return this.httpClient - .post( - !!fileProfileSaveUrl ? fileProfileSaveUrl : this.urls.fileProfileSave, - {}, - { - params: encodeFileProfileSave(req) + const httpReq = new HttpRequest( + 'POST', + !!fileProfileSaveUrl ? fileProfileSaveUrl : this.urls.fileProfileSave, + encodeFileProfileSave(req), + { reportProgress: true, responseType: 'text' as 'json' } + ); + + const progress = req.fileUploadItem.uploadStart(); + + return this.httpClient.request(httpReq).pipe( + filter(event => { + if (event instanceof HttpResponse) { + return true; + } else if (HttpEventType.UploadProgress === event.type) { + progress.next(Math.round((100 * event.loaded) / event.total)); } - ) - .pipe(map(res => decodeFileProfileSave(res))); + return false; + }), + map((event: HttpResponse) => { + req.fileUploadItem.uploadComplete(); + return decodeFileProfileSave(event.body); + }) + ); } public urlForFileTalkDownload( diff --git a/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-side.component.ts b/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-side.component.ts index cb05a429..d510ec74 100644 --- a/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-side.component.ts +++ b/projects/ucap-webmessenger-app/src/app/layouts/messenger/components/left-side.component.ts @@ -16,7 +16,7 @@ import { CreateChatDialogData, CreateChatDialogResult } from '@app/layouts/messenger/dialogs/chat/create-chat.dialog.component'; -import { Observable, Subscription, of } from 'rxjs'; +import { Subscription, of } from 'rxjs'; import { Store, select } from '@ngrx/store'; import * as AppStore from '@app/store'; @@ -28,8 +28,7 @@ import { UserInfoF, UserInfoDN } from '@ucap-webmessenger/protocol-query'; -import { MatTabChangeEvent, MatTabGroup } from '@angular/material'; -import { RightDrawer } from '@app/types'; +import { MatTabChangeEvent } from '@angular/material'; import { LoginResponse } from '@ucap-webmessenger/protocol-authentication'; import { SessionStorageService } from '@ucap-webmessenger/web-storage'; import { KEY_LOGIN_RES_INFO } from '@app/types/login-res-info.type'; @@ -38,7 +37,7 @@ import { KEY_VER_INFO } from '@app/types/ver-info.type'; import { MessageApiService } from '@ucap-webmessenger/api-message'; import { DeviceType } from '@ucap-webmessenger/core'; import { UnreadCountRequest } from 'projects/ucap-webmessenger-api-message/src/lib/apis/unread-count'; -import { map, catchError } from 'rxjs/operators'; +import { map, catchError, tap } from 'rxjs/operators'; import { MessageStatusCode } from '@ucap-webmessenger/api'; import { EditMessageDialogComponent, @@ -87,6 +86,7 @@ export class LeftSideComponent implements OnInit, OnDestroy { sessionVerinfo: VersionInfo2Response; loginRes: LoginResponse; + loginResSubscription: Subscription; constructor( private store: Store, @@ -95,9 +95,6 @@ export class LeftSideComponent implements OnInit, OnDestroy { private messageApiService: MessageApiService, private logger: NGXLogger ) { - this.loginRes = this.sessionStorageService.get( - KEY_LOGIN_RES_INFO - ); this.sessionVerinfo = this.sessionStorageService.get( KEY_VER_INFO ); @@ -112,6 +109,15 @@ export class LeftSideComponent implements OnInit, OnDestroy { this.badgeChatUnReadCount = count; }); + this.loginResSubscription = this.store + .pipe( + select(AppStore.AccountSelector.AuthenticationSelector.loginRes), + tap(loginRes => { + this.loginRes = loginRes; + }) + ) + .subscribe(); + this.getMessageUnreadCount(); this.badgeMessageInterval = setInterval( () => this.getMessageUnreadCount(), @@ -129,6 +135,9 @@ export class LeftSideComponent implements OnInit, OnDestroy { if (!!this.badgeMessageUnReadCountSubscription) { this.badgeMessageUnReadCountSubscription.unsubscribe(); } + if (!!this.loginResSubscription) { + this.loginResSubscription.unsubscribe(); + } if (!!this.badgeMessageInterval) { clearInterval(this.badgeMessageInterval); 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/layouts/messenger/dialogs/profile/profile.dialog.component.html b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/profile/profile.dialog.component.html index c8ab5d39..8649814b 100644 --- a/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/profile/profile.dialog.component.html +++ b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/profile/profile.dialog.component.html @@ -1,5 +1,5 @@ diff --git a/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/profile/profile.dialog.component.ts b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/profile/profile.dialog.component.ts index e387083b..2368e59b 100644 --- a/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/profile/profile.dialog.component.ts +++ b/projects/ucap-webmessenger-app/src/app/layouts/messenger/dialogs/profile/profile.dialog.component.ts @@ -8,6 +8,7 @@ import { Store, select } from '@ngrx/store'; import * as AppStore from '@app/store'; import * as ChatStore from '@app/store/messenger/chat'; import * as SyncStore from '@app/store/messenger/sync'; +import * as AuthenticationStore from '@app/store/account/authentication'; import { UserInfo, GroupDetailData } from '@ucap-webmessenger/protocol-sync'; import { @@ -15,12 +16,28 @@ import { UserInfoF, UserInfoDN } from '@ucap-webmessenger/protocol-query'; -import { DialogService, ConfirmDialogComponent, ConfirmDialogData, ConfirmDialogResult } from '@ucap-webmessenger/ui'; +import { + DialogService, + ConfirmDialogComponent, + ConfirmDialogData, + ConfirmDialogResult, + SnackBarService +} from '@ucap-webmessenger/ui'; import { VersionInfo2Response } from '@ucap-webmessenger/api-public'; import { LoginResponse } from '@ucap-webmessenger/protocol-authentication'; -import { map } from 'rxjs/operators'; +import { map, take, finalize } from 'rxjs/operators'; import { Subscription } from 'rxjs'; -import { SelectGroupDialogComponent, SelectGroupDialogData, SelectGroupDialogResult } from '../group/select-group.dialog.component'; +import { + SelectGroupDialogComponent, + SelectGroupDialogData, + SelectGroupDialogResult +} from '../group/select-group.dialog.component'; +import { + FileUploadItem, + CommonApiService +} from '@ucap-webmessenger/api-common'; +import { EnvironmentsInfo, KEY_ENVIRONMENTS_INFO } from '@app/types'; +import { StatusCode } from '@ucap-webmessenger/api'; export interface ProfileDialogData { userInfo: UserInfo | UserInfoSS | UserInfoF | UserInfoDN; @@ -34,8 +51,11 @@ export interface ProfileDialogResult {} styleUrls: ['./profile.dialog.component.scss'] }) export class ProfileDialogComponent implements OnInit, OnDestroy { + userInfo: UserInfo | UserInfoSS | UserInfoF | UserInfoDN; loginRes: LoginResponse; sessionVerinfo: VersionInfo2Response; + environmentsInfo: EnvironmentsInfo; + isMe: boolean; isBuddy: boolean; isFavorit: boolean; @@ -47,6 +67,8 @@ export class ProfileDialogComponent implements OnInit, OnDestroy { @Inject(MAT_DIALOG_DATA) public data: ProfileDialogData, private dialogService: DialogService, private sessionStorageService: SessionStorageService, + private commonApiService: CommonApiService, + private snackBarService: SnackBarService, private store: Store ) { this.sessionVerinfo = this.sessionStorageService.get( @@ -55,6 +77,11 @@ export class ProfileDialogComponent implements OnInit, OnDestroy { this.loginRes = this.sessionStorageService.get( KEY_LOGIN_RES_INFO ); + this.environmentsInfo = this.sessionStorageService.get( + KEY_ENVIRONMENTS_INFO + ); + + this.userInfo = data.userInfo; } ngOnInit() { @@ -156,8 +183,68 @@ export class ProfileDialogComponent implements OnInit, OnDestroy { this.store.dispatch( SyncStore.delBuddyAndClear({ seq: param.userInfo.seq }) ); - this.isBuddy = false + this.isBuddy = false; } } } + + onUploadProfileImage(profileImageFileUploadItem: FileUploadItem) { + this.commonApiService + .fileProfileSave( + { + userSeq: this.loginRes.userSeq, + deviceType: this.environmentsInfo.deviceType, + token: this.loginRes.tokenString, + file: profileImageFileUploadItem.file, + fileUploadItem: profileImageFileUploadItem + }, + this.sessionVerinfo.profileUploadUrl + ) + .pipe( + take(1), + map(res => { + if (!res) { + return; + } + if (StatusCode.Success === res.statusCode) { + return res; + } else { + throw res; + } + }), + finalize(() => { + setTimeout(() => { + profileImageFileUploadItem.uploadingProgress$ = undefined; + }, 1000); + }) + ) + .subscribe( + res => { + const userInfo = { + ...this.loginRes.userInfo, + profileImageFile: res.profileSubDir + }; + + this.store.dispatch( + AuthenticationStore.updateLoginRes({ + loginRes: { + ...this.loginRes, + userInfo + } + }) + ); + this.userInfo = userInfo as any; + }, + error => { + this.snackBarService.open( + `프로필 이미지 변경중에 문제가 발생하였습니다.`, + '', + { + duration: 3000, + verticalPosition: 'bottom' + } + ); + } + ); + } } diff --git a/projects/ucap-webmessenger-app/src/app/pages/account/components/login.page.component.ts b/projects/ucap-webmessenger-app/src/app/pages/account/components/login.page.component.ts index bad075ab..7734c421 100644 --- a/projects/ucap-webmessenger-app/src/app/pages/account/components/login.page.component.ts +++ b/projects/ucap-webmessenger-app/src/app/pages/account/components/login.page.component.ts @@ -69,7 +69,7 @@ export class LoginPageComponent implements OnInit, OnDestroy { >(AlertDialogComponent, { width: '360px', data: { - title: 'Alert', + title: '로그인', html: `아이디 또는 패스워드가
일치하지 않습니다.` } }); @@ -83,7 +83,7 @@ export class LoginPageComponent implements OnInit, OnDestroy { >(AlertDialogComponent, { width: '360px', data: { - title: 'Alert', + title: '로그인', html: `비밀번호 오류 횟수 초과입니다.
비밀번호를 확인하신 후
잠시 후 다시 시작해 주세요.` } }); diff --git a/projects/ucap-webmessenger-app/src/app/resolvers/messenger.resolver.ts b/projects/ucap-webmessenger-app/src/app/resolvers/messenger.resolver.ts index 932580bf..4f07bfc3 100644 --- a/projects/ucap-webmessenger-app/src/app/resolvers/messenger.resolver.ts +++ b/projects/ucap-webmessenger-app/src/app/resolvers/messenger.resolver.ts @@ -16,7 +16,7 @@ import { import { Store, select } from '@ngrx/store'; -import { ProtocolService } from '@ucap-webmessenger/protocol'; +import { ProtocolService, ServerErrorCode } from '@ucap-webmessenger/protocol'; import { SessionStorageService } from '@ucap-webmessenger/web-storage'; import { PublicApiService, @@ -51,6 +51,7 @@ import { KEY_VER_INFO } from '@app/types/ver-info.type'; import { KEY_LOGIN_RES_INFO } from '@app/types/login-res-info.type'; import { environment } from '../../environments/environment'; +import { SnackBarService } from '@ucap-webmessenger/ui'; @Injectable() export class AppMessengerResolver implements Resolve { @@ -63,6 +64,7 @@ export class AppMessengerResolver implements Resolve { private optionProtocolService: OptionProtocolService, private authenticationProtocolService: AuthenticationProtocolService, private innerProtocolService: InnerProtocolService, + private snackBarService: SnackBarService, private logger: NGXLogger ) {} @@ -101,25 +103,38 @@ export class AppMessengerResolver implements Resolve { }), switchMap(() => this.innerProtocolService.conn({})), switchMap(res => { - return this.authenticationProtocolService.login({ - loginId: loginInfo.loginId, - loginPw: loginInfo.loginPw, - deviceType: environmentsInfo.deviceType, - deviceId: ' ', - token: '', - localeCode: loginInfo.localeCode, - pushId: ' ', - companyCode: loginInfo.companyCode, - passwordEncodingType: 1, - clientVersion: '', - reconnect: false, - ip: 'localhost', - hostName: '', - ssoMode: SSOMode.AUTH, - userSpecificInformation: 'PRO_000482', - productId: environment.productConfig.productId, - productName: environment.productConfig.productName - }); + return this.authenticationProtocolService + .login({ + loginId: loginInfo.loginId, + loginPw: loginInfo.loginPw, + deviceType: environmentsInfo.deviceType, + deviceId: ' ', + token: '', + localeCode: loginInfo.localeCode, + pushId: ' ', + companyCode: loginInfo.companyCode, + passwordEncodingType: 1, + clientVersion: '', + reconnect: false, + ip: 'localhost', + hostName: '', + ssoMode: SSOMode.AUTH, + userSpecificInformation: 'PRO_000482', + productId: environment.productConfig.productId, + productName: environment.productConfig.productName + }) + .pipe( + catchError(err => { + switch (err as ServerErrorCode) { + case ServerErrorCode.ERRCD_IDPW: + break; + + default: + break; + } + return throwError(err); + }) + ); }), switchMap(res => { loginRes = res; 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..b2ddbf81 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,27 @@ 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 }>() +); + +export const updateLoginRes = createAction( + '[Account::Authentication] Update LoginRes', + props<{ + loginRes: LoginResponse; + }>() +); 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..aff0ba2b 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 @@ -16,7 +16,11 @@ import { DialogService, ConfirmDialogComponent, ConfirmDialogData, - ConfirmDialogResult + ConfirmDialogResult, + SnackBarService, + AlertDialogComponent, + AlertDialogData, + AlertDialogResult } from '@ucap-webmessenger/ui'; import { @@ -34,7 +38,10 @@ import { privacyAgreeSuccess, increaseLoginFailCount, initialLoginFailCount, - logoutInitialize + logoutInitialize, + userPasswordSet, + userPasswordSetSuccess, + userPasswordSetFailure } from './actions'; import { LoginInfo, @@ -60,6 +67,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 +284,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 +349,67 @@ 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 }))) + ) + ) + ) + ); + + userPasswordSetSuccess$ = createEffect( + () => { + return this.actions$.pipe( + ofType(userPasswordSetSuccess), + tap(async action => { + await this.dialogService.open< + AlertDialogComponent, + AlertDialogData, + AlertDialogResult + >(AlertDialogComponent, { + width: '360px', + disableClose: true, + data: { + title: '비밀번호 변경', + message: '비밀번호가 변경되었습니다. 다시 로그인하여 주십시오' + } + }); + + this.store.dispatch(logout()); + }) + ); + }, + { dispatch: false } + ); + + userPasswordSetFailure$ = createEffect( + () => { + return this.actions$.pipe( + ofType(userPasswordSetFailure), + tap(action => { + this.snackBarService.open( + `비밀번호 변경 중에 문제가 발생하였습니다.`, + '', + { + duration: 3000, + verticalPosition: 'bottom' + } + ); + }) + ); + }, + { dispatch: false } + ); + constructor( private actions$: Actions, private ngZone: NgZone, @@ -334,8 +420,9 @@ export class Effects { private appAuthenticationService: AppAuthenticationService, private protocolService: ProtocolService, private authenticationProtocolService: AuthenticationProtocolService, - + private serviceProtocolService: ServiceProtocolService, private dialogService: DialogService, + private snackBarService: SnackBarService, @Inject(UCAP_NATIVE_SERVICE) private nativeService: NativeService, private logger: NGXLogger ) {} diff --git a/projects/ucap-webmessenger-app/src/app/store/account/authentication/reducers.ts b/projects/ucap-webmessenger-app/src/app/store/account/authentication/reducers.ts index a00d9ba7..9b305d30 100644 --- a/projects/ucap-webmessenger-app/src/app/store/account/authentication/reducers.ts +++ b/projects/ucap-webmessenger-app/src/app/store/account/authentication/reducers.ts @@ -5,7 +5,8 @@ import { increaseLoginFailCount, initialLoginFailCount, logout, - logoutInitialize + logoutInitialize, + updateLoginRes } from './actions'; export const reducer = createReducer( @@ -17,6 +18,13 @@ export const reducer = createReducer( }; }), + on(updateLoginRes, (state, action) => { + return { + ...state, + loginRes: action.loginRes + }; + }), + on(increaseLoginFailCount, (state, action) => { return { ...state, 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-protocol/src/lib/services/protocol.service.ts b/projects/ucap-webmessenger-protocol/src/lib/services/protocol.service.ts index 3341ff6d..f1abf007 100644 --- a/projects/ucap-webmessenger-protocol/src/lib/services/protocol.service.ts +++ b/projects/ucap-webmessenger-protocol/src/lib/services/protocol.service.ts @@ -10,16 +10,17 @@ import { NGXLogger } from 'ngx-logger'; import { makeWebSocketObservable, GetWebSocketResponses, - NormalClosureMessage, + NormalClosureMessage } from '@ucap-webmessenger/web-socket'; import { PacketBody } from '../protocols/packet'; import { PacketBodyValueDivider, - PacketBodyDivider, + PacketBodyDivider } from '../types/packet-body-divider'; import { PacketBodyValue } from '../types/packet-body-value.type'; -import { SSVC_TYPE_ERROR_RES, ServerErrorCode } from '../types/service'; +import { SSVC_TYPE_ERROR_RES } from '../types/service'; +import { ServerErrorCode } from '../types/error-code'; import { ProtocolMessage } from '../protocols/protocol'; import { _MODULE_CONFIG } from '../config/token'; @@ -37,7 +38,7 @@ interface RequestState { } @Injectable({ - providedIn: 'root', + providedIn: 'root' }) export class ProtocolService { readonly urls: Urls; @@ -186,7 +187,7 @@ export class ProtocolService { packet = this.encodePacket(serviceType, subServiceType, [ ...bodyList, - { type: PacketBodyValue.RequestId, value: requestId }, + { type: PacketBodyValue.RequestId, value: requestId } ]); responseSubject = new Subject().pipe( @@ -209,7 +210,7 @@ export class ProtocolService { this.pendingRequests.set(requestId, { subject: responseSubject, - request: { serviceType, subServiceType, bodyList }, + request: { serviceType, subServiceType, bodyList } }); } else { packet = this.encodePacket(serviceType, subServiceType, bodyList); @@ -302,8 +303,8 @@ export class ProtocolService { serviceType, subServiceType, senderSeq, - bodyList, - }, + bodyList + } }; } diff --git a/projects/ucap-webmessenger-protocol/src/lib/types/error-code.ts b/projects/ucap-webmessenger-protocol/src/lib/types/error-code.ts new file mode 100644 index 00000000..d990eb67 --- /dev/null +++ b/projects/ucap-webmessenger-protocol/src/lib/types/error-code.ts @@ -0,0 +1,48 @@ +export enum ServerErrorCode { + /** 서버측에서 연결을 종료했습니다 */ + ERRCD_FORCE_CLOSE = 10, + /** 통신 프로토콜이 일치하지않습니다 (최신 프로그램으로 업데이트 해주세요) */ + ERRCD_PROTOCOL = 11, + /** 클라이언트 버전이 일치하지않습니다 (최신 프로그램으로 업데이트 해주세요) */ + ERRCD_VERSION = 12, + /** 인증되지않은 기기입니다 */ + ERRCD_DID = 13, + /** 로그인 아이디 또는 패스워드가 일치하지않습니다 */ + ERRCD_IDPW = 14, + /** 다른 디바이스에서 로그인했습니다 */ + ERRCD_DUPLICATE = 15, + /** 인증 토큰이 만료되었습니다 */ + ERRCD_TOKEN = 16, + /** 프로그램을 재설치 바랍니다 */ + ERRCD_SETUP_MAIN = 17, + /** 권한자/관리자에 의해 종료되었습니다 */ + ERRCD_FORCE_INIT = 18, + /** 사용권한이 없습니다 */ + ERRCD_NEED_AUTH_CLIENT = 19, + /** DEMO 서비스 기간이 종료되었습니다 */ + ERRCD_SVC_EXPIRE = 99, + /** 요청 처리를 실패했습니다 */ + ERRCD_FAILED = 100, + /** 데이터 처리를 실패했습니다 */ + ERRCD_DATABASE = 101, + /** 최대치 넘음 */ + ERRCD_EXCESS = 102, + /** 인증이 필요한 요청입니다 */ + ERRCD_NEED_AUTH = 103, + /** */ + ERRCD_USERINFO = 104, + /** 요청 처리를 실패했습니다(에러코드) */ + ERRCD_INVALID_PARAM = 105, + /** 대화방에 함께 참여할 수 없는 사용자가 있습니다 */ + ERRCD_INVALID_INVITE_USER = 106, + /** 상대방이 온라인 상태가 아닙니다 */ + ERRCD_NOT_ONLINE = 200, + /** 패스워드 유효 기간이 만료 되었습니다 */ + ERRCD_PW_EXPIRED = 300, + /** 요청을 처리할 권한이 없습니다 */ + ERRCD_AUTH_DENY = 1000, + /** 암호화 처리 오류 */ + ERRCD_ENCRYPT = 1001, + /** 다른 IP로 접속 요청 */ + ERRCD_RECON = 1002 +} diff --git a/projects/ucap-webmessenger-protocol/src/lib/types/service.ts b/projects/ucap-webmessenger-protocol/src/lib/types/service.ts index 64296725..c9af8e49 100644 --- a/projects/ucap-webmessenger-protocol/src/lib/types/service.ts +++ b/projects/ucap-webmessenger-protocol/src/lib/types/service.ts @@ -1,26 +1 @@ export const SSVC_TYPE_ERROR_RES = 1000; - -export enum ServerErrorCode { - ERRCD_FORCE_CLOSE = 10, - ERRCD_PROTOCOL = 11, - ERRCD_VERSION = 12, - ERRCD_DID = 13, - ERRCD_IDPW = 14, - ERRCD_DUPLICATE = 15, - ERRCD_TOKEN = 16, - ERRCD_SETUP_MAIN = 17, - ERRCD_FORCE_INIT = 18, - ERRCD_SSO_AUTH = 19, - ERRCD_SSO_VALI = 20, - ERRCD_DEV_NOT_SUPPORTED = 21, - ERRCD_NO_MOBILE_PID = 22, - ERRCD_SVC_EXPIRE = 99, - ERRCD_FAILED = 100, - ERRCD_DATABASE = 101, - ERRCD_EXCESS = 102, - ERRCD_NEED_AUTH = 103, - ERRCD_USERINFO = 104, // ucap 은 없음. - ERRCD_AUTH_DENY = 1000, - ERRCD_ENCRYPT = 1001, // 패킷 암호화 오류 - ERRCD_RECON = 1002 // 다른 IP로 접속 요청 -} diff --git a/projects/ucap-webmessenger-protocol/src/public-api.ts b/projects/ucap-webmessenger-protocol/src/public-api.ts index 0f7b82be..2fa0d32f 100644 --- a/projects/ucap-webmessenger-protocol/src/public-api.ts +++ b/projects/ucap-webmessenger-protocol/src/public-api.ts @@ -10,6 +10,7 @@ export * from './lib/services/protocol.service'; export * from './lib/types/packet-body-divider'; export * from './lib/types/packet-body-value.type'; export * from './lib/types/service'; +export * from './lib/types/error-code'; export * from './lib/ucap-protocol.module'; 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..8e897a3b 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,134 @@ - - 현재 패스워드를 입력해 주세요 - 신규 패스워드 - - - 신규 패스워드를 입력해 주세요 - + 신규 패스워드 확인 - - 신규 패스워드 확인을 입력해 주세요 - +
+ + 현재 비밀번호를 입력해 주세요 + + + 현재 비밀번호와 일치하지 않습니다 + + + + 신규 비밀번호를 입력해 주세요 + + + 현재 비밀번호와 동일합니다 + + + + + 비밀번호에는 공백을 입력할 수 없습니다 + + + 사용자 ID를 비밀번호에 포함할 수 없습니다 + + + 사용자 휴대폰번호를 비밀번호에 포함할 수 없습니다 + + + 숫자나 문자를 3번이상 반복적으로 사용할 수 없습니다 + + + 연속되는 숫자나 문자를 3번이상 사용할 수 없습니다 + + + 연속되는 숫자나 문자를 3번이상 사용할 수 없습니다 + + + 문자, 숫자, 특수문자 중 2종류 이상 조합을 해야 합니다 + + + 비밀번호는{{ + newLoginPwFormControl.getError('ucapPassword').result.extra + .combinationCount + }}가지가 조합된 경우{{ + newLoginPwFormControl.getError('ucapPassword').result.extra + .minPwLen + }}자를 넘어야 합니다 + + + + + 신규 비밀번호 확인을 입력해 주세요 + + + 신규 비밀번호와 신규 비밀번호 확인이 다릅니다 + +
+ +
diff --git a/projects/ucap-webmessenger-ui-profile/src/lib/components/profile.component.scss b/projects/ucap-webmessenger-ui-profile/src/lib/components/profile.component.scss index 60953038..2a1c3ef6 100644 --- a/projects/ucap-webmessenger-ui-profile/src/lib/components/profile.component.scss +++ b/projects/ucap-webmessenger-ui-profile/src/lib/components/profile.component.scss @@ -12,12 +12,12 @@ word-wrap: break-word; } } -::ng-deep .mat-card-header-text{ - width:100%; - .mat-card-subtitle{ +::ng-deep .mat-card-header-text { + width: 100%; + .mat-card-subtitle { color: rgb(256, 256, 256, 0.7) !important; - text-align:center; - margin-top:10px !important; + text-align: center; + margin-top: 10px !important; } } @@ -25,78 +25,90 @@ width: 400px; padding: 0 0 20px; position: relative; - .mat-card-header{ + .mat-card-header { justify-content: center; padding-bottom: 40px; background: #76d9c5; /*background: linear-gradient(to right, #345385, #ef4c73);*/ color: #ffffff; padding-top: 20px; - width:100%; - .mat-card-title{ + width: 100%; + .mat-card-title { margin-bottom: 12px; max-width: 100%; justify-content: center; display: flex; - margin:0 20px; - span{ + margin: 0 20px; + span { @include ellipsis(1); } } } - .mat-card-content{ - margin-top:-40px; - .profile-img{ - display:flex; - height:80px; + .mat-card-content { + margin-top: -40px; + .profile-img { + display: flex; + height: 80px; justify-content: center; - margin-bottom:20px; - img{ + margin-bottom: 20px; + img { widows: 80px; height: 80px; border-radius: 50%; - background-color:#efefef; - border:2px solid #ffffff; + background-color: #efefef; + border: 2px solid #ffffff; + } + + .upload-profile-image-spinner { + position: absolute; + top: 90px; + left: 160px; + } + + .upload-profile-image-btn { + position: absolute; + top: 140px; + left: 210px; } } - .profile-option{ - display:flex; - padding:0 20px; - color:#ffffff; + .profile-option { + display: flex; + padding: 0 20px; + color: #ffffff; margin-top: -100px; height: 120px; - .btn-favorite{ + .btn-favorite { cursor: pointer; - .on{ - fill:yellow; + .on { + fill: yellow; } } - .btn-groupadd{ - margin-left:auto; + .btn-groupadd { + margin-left: auto; cursor: pointer; - svg{ - display:none; - &.on{ - display:block; + svg { + display: none; + &.on { + display: block; } } } } - ul{ - padding:0 20px; - display:flex; + ul { + padding: 0 20px; + display: flex; flex-flow: column; - margin-top:-20px; - li{ - display:inline-flex; - height:30px; + margin-top: -20px; + li { + display: inline-flex; + height: 30px; align-items: center; - flex-flow:row; - margin-bottom:20px; + flex-flow: row; + margin-bottom: 20px; - svg{ - margin-right:10px; - color:#777777; + svg { + margin-right: 10px; + color: #777777; } } } diff --git a/projects/ucap-webmessenger-ui-profile/src/lib/components/profile.component.ts b/projects/ucap-webmessenger-ui-profile/src/lib/components/profile.component.ts index a7612d97..e1a84944 100644 --- a/projects/ucap-webmessenger-ui-profile/src/lib/components/profile.component.ts +++ b/projects/ucap-webmessenger-ui-profile/src/lib/components/profile.component.ts @@ -1,11 +1,16 @@ -import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core'; +import { + Component, + OnInit, + Input, + EventEmitter, + Output, + ViewChild, + ElementRef +} from '@angular/core'; import { UserInfo } from '@ucap-webmessenger/protocol-sync'; -import { - UserInfoSS, - UserInfoF, - UserInfoDN -} from '@ucap-webmessenger/protocol-query'; +import { UserInfoF } from '@ucap-webmessenger/protocol-query'; +import { FileUploadItem } from '@ucap-webmessenger/api-common'; @Component({ selector: 'ucap-profile-profile', @@ -37,6 +42,14 @@ export class ProfileComponent implements OnInit { isBuddy: boolean; }>(); + @Output() + uploadProfileImage = new EventEmitter(); + + @ViewChild('profileImageFileInput', { static: false }) + profileImageFileInput: ElementRef; + + profileImageFileUploadItem: FileUploadItem; + constructor() {} ngOnInit() {} @@ -73,4 +86,14 @@ export class ProfileComponent implements OnInit { isBuddy: false }); } + + onChangeFileInput() { + this.profileImageFileUploadItem = FileUploadItem.fromFiles( + this.profileImageFileInput.nativeElement.files + )[0]; + + this.uploadProfileImage.emit(this.profileImageFileUploadItem); + + this.profileImageFileInput.nativeElement.value = ''; + } } diff --git a/projects/ucap-webmessenger-ui-profile/src/lib/ucap-ui-profile.module.ts b/projects/ucap-webmessenger-ui-profile/src/lib/ucap-ui-profile.module.ts index 1849a2ad..feb12152 100644 --- a/projects/ucap-webmessenger-ui-profile/src/lib/ucap-ui-profile.module.ts +++ b/projects/ucap-webmessenger-ui-profile/src/lib/ucap-ui-profile.module.ts @@ -8,6 +8,7 @@ import { FlexLayoutModule } from '@angular/flex-layout'; import { MatRippleModule, MatCheckboxModule } from '@angular/material'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { UCapUiModule } from '@ucap-webmessenger/ui'; @@ -35,6 +36,7 @@ const SERVICES = []; MatCheckboxModule, MatCardModule, MatTooltipModule, + MatProgressSpinnerModule, UCapUiModule ], diff --git a/projects/ucap-webmessenger-ui/src/lib/directives/image.directive.ts b/projects/ucap-webmessenger-ui/src/lib/directives/image.directive.ts index 412f15fb..0ce4bdfa 100644 --- a/projects/ucap-webmessenger-ui/src/lib/directives/image.directive.ts +++ b/projects/ucap-webmessenger-ui/src/lib/directives/image.directive.ts @@ -5,15 +5,19 @@ import { Output, Input, AfterViewInit, - OnInit + OnInit, + OnChanges, + SimpleChanges } from '@angular/core'; import { NGXLogger } from 'ngx-logger'; +const PATH = 'path'; + @Directive({ selector: 'img[ucapImage]' }) -export class ImageDirective implements OnInit, AfterViewInit { +export class ImageDirective implements OnInit, AfterViewInit, OnChanges { @Input() base: string; @@ -50,6 +54,18 @@ export class ImageDirective implements OnInit, AfterViewInit { } ngAfterViewInit(): void { + this.loadImage(); + } + + ngOnChanges(changes: SimpleChanges): void { + const pathChanges = changes[PATH]; + + if (!!pathChanges && !pathChanges.firstChange) { + this.loadImage(); + } + } + + private loadImage(): void { if (this.imageSrc === this.default) { this.elementRef.nativeElement.src = this.default; this.loaded.emit(this.elementRef.nativeElement); diff --git a/projects/ucap-webmessenger-ui/src/lib/services/snack-bar.service.spec.ts b/projects/ucap-webmessenger-ui/src/lib/services/snack-bar.service.spec.ts index 97fb5eb4..14afffd2 100644 --- a/projects/ucap-webmessenger-ui/src/lib/services/snack-bar.service.spec.ts +++ b/projects/ucap-webmessenger-ui/src/lib/services/snack-bar.service.spec.ts @@ -1,12 +1,12 @@ import { TestBed } from '@angular/core/testing'; -import { BottomSheetService } from './bottom-sheet.service'; +import { SnackBarService } from './snack-bar.service'; -describe('ui::BottomSheetService', () => { +describe('ui::SnackBarService', () => { beforeEach(() => TestBed.configureTestingModule({})); it('should be created', () => { - const service: BottomSheetService = TestBed.get(BottomSheetService); + const service: SnackBarService = TestBed.get(SnackBarService); expect(service).toBeTruthy(); }); });