auto login is implemented

This commit is contained in:
병준 박 2019-12-15 22:49:35 +09:00
parent 497f04efca
commit 0b8c11de48
17 changed files with 288 additions and 65 deletions

View File

@ -0,0 +1,60 @@
import { Injectable } from '@angular/core';
import {
CanActivate,
ActivatedRouteSnapshot,
RouterStateSnapshot,
UrlTree,
Router
} from '@angular/router';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
import { LocalStorageService } from '@ucap-webmessenger/web-storage';
import { AppUserInfo, KEY_APP_USER_INFO } from '@app/types/app-user-info.type';
import { environment } from '../../environments/environment';
import * as AuthenticationStore from '@app/store/account/authentication';
@Injectable({
providedIn: 'root'
})
export class AppAutoLoginGuard implements CanActivate {
constructor(
private router: Router,
private store: Store<any>,
private localStorageService: LocalStorageService
) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
):
| boolean
| UrlTree
| Observable<boolean | UrlTree>
| Promise<boolean | UrlTree> {
return new Promise<boolean | UrlTree>((resolve, reject) => {
const appUserInfo = this.localStorageService.encGet<AppUserInfo>(
KEY_APP_USER_INFO,
environment.customConfig.appKey
);
if (!!appUserInfo && appUserInfo.autoLogin) {
this.store.dispatch(
AuthenticationStore.webLogin({
loginInfo: {
companyCode: appUserInfo.companyCode,
companyGroupType: appUserInfo.companyGroupType,
loginId: appUserInfo.loginId,
loginPw: appUserInfo.loginPw
},
rememberMe: appUserInfo.rememberMe,
autoLogin: appUserInfo.autoLogin
})
);
resolve(false);
} else {
resolve(true);
}
});
}
}

View File

@ -2,12 +2,14 @@ import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; import { Routes, RouterModule } from '@angular/router';
import { LoginPageComponent } from './components/login.page.component'; import { LoginPageComponent } from './components/login.page.component';
import { AppAutoLoginGuard } from '@app/guards/auto-login.guard';
const routes: Routes = [ const routes: Routes = [
{ path: '', redirectTo: '/account/login', pathMatch: 'full' }, { path: '', redirectTo: '/account/login', pathMatch: 'full' },
{ {
path: 'login', path: 'login',
component: LoginPageComponent component: LoginPageComponent,
canActivate: [AppAutoLoginGuard]
} }
]; ];

View File

@ -6,6 +6,12 @@
[notiText]="fixedNotiBtnText" [notiText]="fixedNotiBtnText"
[loginBtnEnable]="loginBtnEnable" [loginBtnEnable]="loginBtnEnable"
[loginBtnText]="loginBtnText" [loginBtnText]="loginBtnText"
[companyCode]="appUserInfo?.companyCode"
[loginId]="appUserInfo?.loginId"
[rememberMe]="appUserInfo?.rememberMe"
[autoLogin]="appUserInfo?.autoLogin"
[useRememberMe]="useRememberMe"
[useAutoLogin]="useAutoLogin"
(login)="onLogin($event)" (login)="onLogin($event)"
(notiClick)="onClickNoti($event)" (notiClick)="onClickNoti($event)"
> >

View File

@ -23,6 +23,10 @@ import {
NoticeDialogData NoticeDialogData
} from '@app/layouts/messenger/dialogs/account/notice.dialog.component'; } from '@app/layouts/messenger/dialogs/account/notice.dialog.component';
import { environment } from '../../../../environments/environment';
import { LocalStorageService } from '@ucap-webmessenger/web-storage';
import { AppUserInfo, KEY_APP_USER_INFO } from '@app/types/app-user-info.type';
@Component({ @Component({
selector: 'app-page-account-login', selector: 'app-page-account-login',
templateUrl: './login.page.component.html', templateUrl: './login.page.component.html',
@ -44,11 +48,25 @@ export class LoginPageComponent implements OnInit, OnDestroy {
defatulWaitingTime: number; defatulWaitingTime: number;
waitingTime: number; waitingTime: number;
appUserInfo: AppUserInfo;
useRememberMe: boolean;
useAutoLogin: boolean;
constructor( constructor(
private store: Store<any>, private store: Store<any>,
private router: Router, private router: Router,
private dialogService: DialogService private dialogService: DialogService,
) {} private localStorageService: LocalStorageService
) {
this.useRememberMe =
environment.productConfig.authentication.rememberMe.use;
this.useAutoLogin = environment.productConfig.authentication.autoLogin.use;
this.appUserInfo = this.localStorageService.encGet<AppUserInfo>(
KEY_APP_USER_INFO,
environment.customConfig.appKey
);
}
ngOnInit(): void { ngOnInit(): void {
this.defatulLoginBtnText = 'LOGIN'; this.defatulLoginBtnText = 'LOGIN';
@ -152,6 +170,7 @@ export class LoginPageComponent implements OnInit, OnDestroy {
loginId: string; loginId: string;
loginPw: string; loginPw: string;
rememberMe: boolean; rememberMe: boolean;
autoLogin: boolean;
notValid: () => void; notValid: () => void;
}) { }) {
this.store.dispatch( this.store.dispatch(
@ -162,7 +181,8 @@ export class LoginPageComponent implements OnInit, OnDestroy {
loginId: value.loginId, loginId: value.loginId,
loginPw: value.loginPw loginPw: value.loginPw
}, },
rememberMe: value.rememberMe rememberMe: value.rememberMe,
autoLogin: value.autoLogin
}) })
); );
} }

View File

@ -14,6 +14,9 @@ import {
import { PasswordUtil } from '@ucap-webmessenger/pi'; import { PasswordUtil } from '@ucap-webmessenger/pi';
import { DaesangCipherService } from '@ucap-webmessenger/daesang'; import { DaesangCipherService } from '@ucap-webmessenger/daesang';
import { environment } from '../../environments/environment';
import { AppUserInfo, KEY_APP_USER_INFO } from '@app/types/app-user-info.type';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
@ -31,25 +34,33 @@ export class AppAuthenticationService {
return null !== loginInfo && !!loginInfo.loginId; return null !== loginInfo && !!loginInfo.loginId;
} }
login(loginInfo: LoginInfo, rememberMe: boolean) { login(loginInfo: LoginInfo, rememberMe: boolean, autoLogin: boolean) {
loginInfo = { ...loginInfo, localeCode: LocaleCode.Korean }; loginInfo = { ...loginInfo, localeCode: LocaleCode.Korean };
const encLoginPw = this.daesangCipherService.encrypt(
environment.customConfig.pw.userKey,
loginInfo.loginPw,
environment.customConfig.pw.isBase64
);
// PasswordUtil.encrypt(loginInfo.loginPw)
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: PasswordUtil.encrypt(loginInfo.loginPw) loginPw: encLoginPw
loginPw: this.daesangCipherService.encrypt(
'DaesangSSOProject',
loginInfo.loginPw,
'N'
)
}); });
if (rememberMe) { if (rememberMe || autoLogin) {
this.localStorageService.set<LoginInfo>(KEY_LOGIN_INFO, { this.localStorageService.encSet<AppUserInfo>(
...loginInfo, KEY_APP_USER_INFO,
loginPw: undefined {
}); ...loginInfo,
loginPw: autoLogin ? loginInfo.loginPw : undefined,
rememberMe,
autoLogin
},
environment.customConfig.appKey
);
} else { } else {
this.localStorageService.remove(KEY_LOGIN_INFO); this.localStorageService.remove(KEY_LOGIN_INFO);
} }

View File

@ -14,6 +14,7 @@ export const webLogin = createAction(
props<{ props<{
loginInfo: LoginInfo; loginInfo: LoginInfo;
rememberMe: boolean; rememberMe: boolean;
autoLogin: boolean;
}>() }>()
); );
@ -22,6 +23,7 @@ export const webLoginSuccess = createAction(
props<{ props<{
loginInfo: LoginInfo; loginInfo: LoginInfo;
rememberMe: boolean; rememberMe: boolean;
autoLogin: boolean;
login2Response: Login2Response; login2Response: Login2Response;
}>() }>()
); );

View File

@ -78,29 +78,35 @@ export class Effects {
this.actions$.pipe( this.actions$.pipe(
ofType(webLogin), ofType(webLogin),
map(action => action), map(action => action),
exhaustMap((params: { loginInfo: LoginInfo; rememberMe: boolean }) => exhaustMap(
this.piService (params: {
.login2({ loginInfo: LoginInfo;
loginId: params.loginInfo.loginId, rememberMe: boolean;
loginPw: params.loginInfo.loginPw, autoLogin: boolean;
companyCode: params.loginInfo.companyCode }) =>
}) this.piService
.pipe( .login2({
map((res: Login2Response) => { loginId: params.loginInfo.loginId,
if ('success' !== res.status.toLowerCase()) { loginPw: params.loginInfo.loginPw,
this.store.dispatch(increaseLoginFailCount({})); companyCode: params.loginInfo.companyCode
return webLoginFailure({ error: 'Failed' }); })
} else { .pipe(
this.store.dispatch(initialLoginFailCount({})); map((res: Login2Response) => {
return webLoginSuccess({ if ('success' !== res.status.toLowerCase()) {
loginInfo: params.loginInfo, this.store.dispatch(increaseLoginFailCount({}));
rememberMe: params.rememberMe, return webLoginFailure({ error: 'Failed' });
login2Response: res } else {
}); this.store.dispatch(initialLoginFailCount({}));
} return webLoginSuccess({
}), loginInfo: params.loginInfo,
catchError(error => of(webLoginFailure({ error }))) rememberMe: params.rememberMe,
) autoLogin: params.autoLogin,
login2Response: res
});
}
}),
catchError(error => of(webLoginFailure({ error })))
)
) )
) )
); );
@ -118,7 +124,8 @@ export class Effects {
if (!update) { if (!update) {
this.appAuthenticationService.login( this.appAuthenticationService.login(
params.loginInfo, params.loginInfo,
params.rememberMe params.rememberMe,
params.autoLogin
); );
this.router.navigate(['/messenger']); this.router.navigate(['/messenger']);
} }

View File

@ -0,0 +1,13 @@
import { LocaleCode } from '@ucap-webmessenger/core';
export const KEY_APP_USER_INFO = 'ucap::APP_USER_INFO';
export interface AppUserInfo {
loginId?: string;
loginPw?: string;
rememberMe?: boolean;
autoLogin?: boolean;
companyCode?: string;
companyGroupType?: string;
localeCode?: LocaleCode;
}

View File

@ -22,7 +22,13 @@ export const environment: Environment = {
productId: 'PRO_000482', productId: 'PRO_000482',
productName: 'EZMessenger', productName: 'EZMessenger',
authentication: { authentication: {
usePrivateInformationAgree: false usePrivateInformationAgree: false,
rememberMe: {
use: false
},
autoLogin: {
use: true
}
}, },
updateCheckConfig: { updateCheckConfig: {
deviceType: DeviceType.Renderer, deviceType: DeviceType.Renderer,
@ -30,6 +36,14 @@ export const environment: Environment = {
} }
}, },
customConfig: {
pw: {
userKey: 'DaesangSSOProject',
isBase64: 'N'
},
appKey: '!@#$DAESANG%^&*'
},
commonApiModuleConfig: { commonApiModuleConfig: {
hostConfig: { hostConfig: {
protocol: 'http', protocol: 'http',

View File

@ -22,7 +22,13 @@ export const environment: Environment = {
productId: 'PRO_000482', productId: 'PRO_000482',
productName: 'EZMessenger', productName: 'EZMessenger',
authentication: { authentication: {
usePrivateInformationAgree: false usePrivateInformationAgree: false,
rememberMe: {
use: false
},
autoLogin: {
use: true
}
}, },
updateCheckConfig: { updateCheckConfig: {
deviceType: DeviceType.Renderer, deviceType: DeviceType.Renderer,
@ -30,6 +36,14 @@ export const environment: Environment = {
} }
}, },
customConfig: {
pw: {
userKey: 'DaesangSSOProject',
isBase64: 'N'
},
appKey: '!@#$DAESANG%^&*'
},
commonApiModuleConfig: { commonApiModuleConfig: {
hostConfig: { hostConfig: {
protocol: 'http', protocol: 'http',

View File

@ -22,7 +22,13 @@ export const environment: Environment = {
productId: 'PRO_000482', productId: 'PRO_000482',
productName: 'EZMessenger', productName: 'EZMessenger',
authentication: { authentication: {
usePrivateInformationAgree: false usePrivateInformationAgree: false,
rememberMe: {
use: false
},
autoLogin: {
use: true
}
}, },
updateCheckConfig: { updateCheckConfig: {
deviceType: DeviceType.Renderer, deviceType: DeviceType.Renderer,

View File

@ -22,7 +22,13 @@ export const environment: Environment = {
productId: 'PRO_000482', productId: 'PRO_000482',
productName: 'EZMessenger', productName: 'EZMessenger',
authentication: { authentication: {
usePrivateInformationAgree: false usePrivateInformationAgree: false,
rememberMe: {
use: false
},
autoLogin: {
use: true
}
}, },
updateCheckConfig: { updateCheckConfig: {
deviceType: DeviceType.Renderer, deviceType: DeviceType.Renderer,

View File

@ -44,6 +44,12 @@ export interface Environment {
productName: string; productName: string;
authentication: { authentication: {
usePrivateInformationAgree: boolean; usePrivateInformationAgree: boolean;
rememberMe: {
use: boolean;
};
autoLogin: {
use: boolean;
};
}; };
updateCheckConfig: { updateCheckConfig: {
deviceType: DeviceType; deviceType: DeviceType;
@ -51,6 +57,8 @@ export interface Environment {
}; };
}; };
customConfig?: any;
commonApiModuleConfig: CommonApiModuleConfig; commonApiModuleConfig: CommonApiModuleConfig;
publicApiModuleConfig: PublicApiModuleConfig; publicApiModuleConfig: PublicApiModuleConfig;
externalApiModuleConfig: ExternalApiModuleConfig; externalApiModuleConfig: ExternalApiModuleConfig;

View File

@ -42,16 +42,22 @@
fxLayoutAlign="space-between center" fxLayoutAlign="space-between center"
> >
<mat-checkbox <mat-checkbox
*ngIf="useRememberMe"
class="remember-me" class="remember-me"
formControlName="remember" formControlName="rememberMe"
aria-label="Remember Me" aria-label="Remember Me"
> >
Remember Me 아이디 저장
</mat-checkbox> </mat-checkbox>
<a class="forgot-password"> <mat-checkbox
Forgot Password? *ngIf="useAutoLogin"
</a> class="auto-login"
formControlName="autoLogin"
aria-label="Auto Login"
>
자동 로그인
</mat-checkbox>
</div> </div>
<button <button
@ -67,6 +73,7 @@
<div class="register" fxLayout="column" fxLayoutAlign="center center"> <div class="register" fxLayout="column" fxLayoutAlign="center center">
<span class="text">Don't have an account?</span> <span class="text">Don't have an account?</span>
<a class="link">Forgot Password?</a>
<a class="link">Create an account</a> <a class="link">Create an account</a>
</div> </div>
<div class="policy"> <div class="policy">

View File

@ -32,7 +32,7 @@
margin-bottom: 16px; margin-bottom: 16px;
} }
.forgot-password { .auto-login {
font-size: 13px; font-size: 13px;
font-weight: 600; font-weight: 600;
margin-bottom: 16px; margin-bottom: 16px;

View File

@ -10,8 +10,6 @@ import {
} from '@angular/core'; } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Company } from '@ucap-webmessenger/api-external'; import { Company } from '@ucap-webmessenger/api-external';
import { LocalStorageService } from '@ucap-webmessenger/web-storage';
import { LoginInfo, KEY_LOGIN_INFO } from '@app/types';
@Component({ @Component({
selector: 'ucap-account-login', selector: 'ucap-account-login',
@ -30,12 +28,27 @@ export class LoginComponent implements OnInit {
@Input() @Input()
notiText?: string; notiText?: string;
@Input()
companyCode: string;
@Input()
loginId: string;
@Input()
rememberMe: boolean;
@Input()
autoLogin: boolean;
@Input()
useRememberMe: boolean;
@Input()
useAutoLogin: boolean;
@Output() @Output()
login = new EventEmitter<{ login = new EventEmitter<{
companyCode: string; companyCode: string;
loginId: string; loginId: string;
loginPw: string; loginPw: string;
rememberMe: boolean; rememberMe: boolean;
autoLogin: boolean;
notValid: () => void; notValid: () => void;
}>(); }>();
@Output() @Output()
@ -47,35 +60,37 @@ export class LoginComponent implements OnInit {
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>(
KEY_LOGIN_INFO
);
this.loginForm = this.formBuilder.group({ this.loginForm = this.formBuilder.group({
companyCode: ['', [Validators.required]], companyCode: ['', [Validators.required]],
loginId: ['', [Validators.required]], loginId: ['', [Validators.required]],
loginPw: ['', Validators.required], loginPw: ['', Validators.required],
remember: [false] rememberMe: [false],
autoLogin: [false]
}); });
if (!!this.rememberMe && this.rememberMe) {
this.loginForm.get('rememberMe').setValue(true);
}
if (!!this.autoLogin && this.autoLogin) {
this.loginForm.get('autoLogin').setValue(true);
}
if (!!this.curCompanyCode) { if (!!this.curCompanyCode) {
this.loginForm.get('companyCode').setValue(this.curCompanyCode); this.loginForm.get('companyCode').setValue(this.curCompanyCode);
} }
if (!!this.loginId) {
if (loginInfo && loginInfo.companyCode && loginInfo.loginId) { this.loginForm.get('loginId').setValue(this.loginId);
this.loginForm.get('companyCode').setValue(loginInfo.companyCode);
this.loginForm.get('loginId').setValue(loginInfo.loginId);
this.loginForm.get('remember').setValue(true);
this.changeDetectorRef.detectChanges();
} }
if (!this.loginBtnText || this.loginBtnText.trim().length === 0) { if (!this.loginBtnText || this.loginBtnText.trim().length === 0) {
this.loginBtnText = 'LOGIN'; this.loginBtnText = 'LOGIN';
} }
this.changeDetectorRef.detectChanges();
} }
onClickLogin() { onClickLogin() {
@ -83,7 +98,8 @@ export class LoginComponent implements OnInit {
companyCode: this.loginForm.get('companyCode').value, companyCode: this.loginForm.get('companyCode').value,
loginId: this.loginForm.get('loginId').value, loginId: this.loginForm.get('loginId').value,
loginPw: this.loginForm.get('loginPw').value, loginPw: this.loginForm.get('loginPw').value,
rememberMe: this.loginForm.get('remember').value, rememberMe: this.loginForm.get('rememberMe').value,
autoLogin: this.loginForm.get('autoLogin').value,
notValid: () => { notValid: () => {
this.loginPwElementRef.nativeElement.focus(); this.loginPwElementRef.nativeElement.focus();
} }

View File

@ -2,6 +2,8 @@ import { StorageUtil, ExpiredUnit } from '../utils/storage.util';
import { fromEvent, Observable } from 'rxjs'; import { fromEvent, Observable } from 'rxjs';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import CryptoJS from 'crypto-js';
export class StorageService { export class StorageService {
constructor(private storage: Storage) {} constructor(private storage: Storage) {}
@ -18,6 +20,35 @@ export class StorageService {
return StorageUtil.set(this.storage, key, value, expiredAt, expiredUnit); return StorageUtil.set(this.storage, key, value, expiredAt, expiredUnit);
} }
encGet<T>(key: string, secretPassphrase: string): T | null {
const encrypted = StorageUtil.get(this.storage, key) as CryptoJS.WordArray;
if (!encrypted) {
return null;
}
const decrypted = CryptoJS.AES.decrypt(encrypted, secretPassphrase);
const json = decrypted.toString(CryptoJS.enc.Utf8);
return JSON.parse(json) as T;
}
encSet<T>(
key: string,
value: T,
secretPassphrase: string,
expiredAt: number = 0,
expiredUnit: ExpiredUnit = 'd'
) {
const json = JSON.stringify(value);
const encrypted = CryptoJS.AES.encrypt(json, secretPassphrase);
return StorageUtil.set(
this.storage,
key,
encrypted.toString(),
expiredAt,
expiredUnit
);
}
remove(key: string | RegExp) { remove(key: string | RegExp) {
if (typeof key === 'string') { if (typeof key === 'string') {
StorageUtil.remove(this.storage, key); StorageUtil.remove(this.storage, key);