This repository has been archived by the owner on Jun 17, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 137
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[SSO] Merge feature/sso into master (#139)
* [SSO] Reset Master Password (#134) * Initial commit of reset master password (sso) * Updated line length error * Updated import line again * Added trailing comma * restored reference data for RegisterRequest * Updated tracking boolean name // added success route update based on passed boolean * Added new API // reverted Register // deleted reset // added change pw and sso * Changed redirect URI to protected to override in sub-class * Updated api to setPassword // Updated request model name // Updated change password refs // Updated formatting * Encoded necessary parts of authorize url // Added default catch error message * Refactored methods inside change password base component // removed unnecesary query param for sso * [lint] Fixed error (#137) * Cleaned lint error * Fixed sso lint error
- Loading branch information
1 parent
261a200
commit f301b92
Showing
6 changed files
with
295 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
import { | ||
OnInit, | ||
} from '@angular/core'; | ||
|
||
import { | ||
Router, | ||
} from '@angular/router'; | ||
|
||
import { ApiService } from '../../abstractions/api.service'; | ||
import { CipherService } from '../../abstractions/cipher.service'; | ||
import { CryptoService } from '../../abstractions/crypto.service'; | ||
import { FolderService } from '../../abstractions/folder.service'; | ||
import { I18nService } from '../../abstractions/i18n.service'; | ||
import { MessagingService } from '../../abstractions/messaging.service'; | ||
import { PasswordGenerationService } from '../../abstractions/passwordGeneration.service'; | ||
import { PlatformUtilsService } from '../../abstractions/platformUtils.service'; | ||
import { PolicyService } from '../../abstractions/policy.service'; | ||
import { SyncService } from '../../abstractions/sync.service'; | ||
import { UserService } from '../../abstractions/user.service'; | ||
|
||
import { CipherString } from '../../models/domain/cipherString'; | ||
import { MasterPasswordPolicyOptions } from '../../models/domain/masterPasswordPolicyOptions'; | ||
import { SymmetricCryptoKey } from '../../models/domain/symmetricCryptoKey'; | ||
|
||
export class ChangePasswordComponent implements OnInit { | ||
newMasterPassword: string; | ||
confirmNewMasterPassword: string; | ||
formPromise: Promise<any>; | ||
masterPasswordScore: number; | ||
enforcedPolicyOptions: MasterPasswordPolicyOptions; | ||
|
||
private masterPasswordStrengthTimeout: any; | ||
private email: string; | ||
|
||
constructor(protected apiService: ApiService, protected i18nService: I18nService, | ||
protected cryptoService: CryptoService, protected messagingService: MessagingService, | ||
protected userService: UserService, protected passwordGenerationService: PasswordGenerationService, | ||
protected platformUtilsService: PlatformUtilsService, protected folderService: FolderService, | ||
protected cipherService: CipherService, protected syncService: SyncService, | ||
protected policyService: PolicyService, protected router: Router) { } | ||
|
||
async ngOnInit() { | ||
this.email = await this.userService.getEmail(); | ||
this.enforcedPolicyOptions = await this.policyService.getMasterPasswordPolicyOptions(); | ||
} | ||
|
||
getPasswordScoreAlertDisplay() { | ||
if (this.enforcedPolicyOptions == null) { | ||
return ''; | ||
} | ||
|
||
let str: string; | ||
switch (this.enforcedPolicyOptions.minComplexity) { | ||
case 4: | ||
str = this.i18nService.t('strong'); | ||
break; | ||
case 3: | ||
str = this.i18nService.t('good'); | ||
break; | ||
default: | ||
str = this.i18nService.t('weak'); | ||
break; | ||
} | ||
return str + ' (' + this.enforcedPolicyOptions.minComplexity + ')'; | ||
} | ||
|
||
async submit() { | ||
const hasEncKey = await this.cryptoService.hasEncKey(); | ||
if (!hasEncKey) { | ||
this.platformUtilsService.showToast('error', null, this.i18nService.t('updateKey')); | ||
return; | ||
} | ||
|
||
if (this.newMasterPassword == null || this.newMasterPassword === '') { | ||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), | ||
this.i18nService.t('masterPassRequired')); | ||
return; | ||
} | ||
if (this.newMasterPassword.length < 8) { | ||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), | ||
this.i18nService.t('masterPassLength')); | ||
return; | ||
} | ||
if (this.newMasterPassword !== this.confirmNewMasterPassword) { | ||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), | ||
this.i18nService.t('masterPassDoesntMatch')); | ||
return; | ||
} | ||
|
||
const strengthResult = this.passwordGenerationService.passwordStrength(this.newMasterPassword, | ||
this.getPasswordStrengthUserInput()); | ||
|
||
if (this.enforcedPolicyOptions != null && | ||
!this.policyService.evaluateMasterPassword( | ||
strengthResult.score, | ||
this.newMasterPassword, | ||
this.enforcedPolicyOptions)) { | ||
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'), | ||
this.i18nService.t('masterPasswordPolicyRequirementsNotMet')); | ||
return; | ||
} | ||
|
||
if (strengthResult != null && strengthResult.score < 3) { | ||
const result = await this.platformUtilsService.showDialog(this.i18nService.t('weakMasterPasswordDesc'), | ||
this.i18nService.t('weakMasterPassword'), this.i18nService.t('yes'), this.i18nService.t('no'), | ||
'warning'); | ||
if (!result) { | ||
return; | ||
} | ||
} | ||
|
||
if (!await this.setupSubmitActions()) { | ||
return; | ||
} | ||
|
||
const email = await this.userService.getEmail(); | ||
const kdf = await this.userService.getKdf(); | ||
const kdfIterations = await this.userService.getKdfIterations(); | ||
const newKey = await this.cryptoService.makeKey(this.newMasterPassword, email.trim().toLowerCase(), | ||
kdf, kdfIterations); | ||
const newMasterPasswordHash = await this.cryptoService.hashPassword(this.newMasterPassword, newKey); | ||
const newEncKey = await this.cryptoService.remakeEncKey(newKey); | ||
|
||
await this.performSubmitActions(newMasterPasswordHash, newKey, newEncKey); | ||
} | ||
|
||
async setupSubmitActions(): Promise<boolean> { | ||
// Override in sub-class | ||
// Can be used for additional validation and/or other processes the should occur before changing passwords | ||
return true; | ||
} | ||
|
||
async performSubmitActions(newMasterPasswordHash: string, newKey: SymmetricCryptoKey, | ||
newEncKey: [SymmetricCryptoKey, CipherString]) { | ||
// Override in sub-class | ||
} | ||
|
||
updatePasswordStrength() { | ||
if (this.masterPasswordStrengthTimeout != null) { | ||
clearTimeout(this.masterPasswordStrengthTimeout); | ||
} | ||
this.masterPasswordStrengthTimeout = setTimeout(() => { | ||
const strengthResult = this.passwordGenerationService.passwordStrength(this.newMasterPassword, | ||
this.getPasswordStrengthUserInput()); | ||
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score; | ||
}, 300); | ||
} | ||
|
||
private getPasswordStrengthUserInput() { | ||
let userInput: string[] = []; | ||
const atPosition = this.email.indexOf('@'); | ||
if (atPosition > -1) { | ||
userInput = userInput.concat(this.email.substr(0, atPosition).trim().toLowerCase().split(/[^A-Za-z0-9]/)); | ||
} | ||
return userInput; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import { | ||
ActivatedRoute, | ||
Router, | ||
} from '@angular/router'; | ||
|
||
import { ApiService } from '../../abstractions/api.service'; | ||
import { AuthService } from '../../abstractions/auth.service'; | ||
import { CryptoFunctionService } from '../../abstractions/cryptoFunction.service'; | ||
import { I18nService } from '../../abstractions/i18n.service'; | ||
import { PasswordGenerationService } from '../../abstractions/passwordGeneration.service'; | ||
import { PlatformUtilsService } from '../../abstractions/platformUtils.service'; | ||
import { StateService } from '../../abstractions/state.service'; | ||
import { StorageService } from '../../abstractions/storage.service'; | ||
|
||
import { ConstantsService } from '../../services/constants.service'; | ||
|
||
import { Utils } from '../../misc/utils'; | ||
|
||
import { AuthResult } from '../../models/domain/authResult'; | ||
|
||
export class SsoComponent { | ||
identifier: string; | ||
loggingIn = false; | ||
|
||
formPromise: Promise<AuthResult>; | ||
onSuccessfulLogin: () => Promise<any>; | ||
onSuccessfulLoginNavigate: () => Promise<any>; | ||
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>; | ||
onSuccessfulLoginChangePasswordNavigate: () => Promise<any>; | ||
|
||
protected twoFactorRoute = '2fa'; | ||
protected successRoute = 'lock'; | ||
protected changePasswordRoute = 'change-password'; | ||
protected redirectUri: string; | ||
|
||
constructor(protected authService: AuthService, protected router: Router, | ||
protected i18nService: I18nService, protected route: ActivatedRoute, | ||
protected storageService: StorageService, protected stateService: StateService, | ||
protected platformUtilsService: PlatformUtilsService, protected apiService: ApiService, | ||
protected cryptoFunctionService: CryptoFunctionService, | ||
protected passwordGenerationService: PasswordGenerationService) { } | ||
|
||
async ngOnInit() { | ||
const queryParamsSub = this.route.queryParams.subscribe(async (qParams) => { | ||
if (qParams.code != null && qParams.state != null) { | ||
const codeVerifier = await this.storageService.get<string>(ConstantsService.ssoCodeVerifierKey); | ||
const state = await this.storageService.get<string>(ConstantsService.ssoStateKey); | ||
await this.storageService.remove(ConstantsService.ssoCodeVerifierKey); | ||
await this.storageService.remove(ConstantsService.ssoStateKey); | ||
if (qParams.code != null && codeVerifier != null && state != null && state === qParams.state) { | ||
await this.logIn(qParams.code, codeVerifier); | ||
} | ||
} | ||
if (queryParamsSub != null) { | ||
queryParamsSub.unsubscribe(); | ||
} | ||
}); | ||
} | ||
|
||
async submit() { | ||
const passwordOptions: any = { | ||
type: 'password', | ||
length: 64, | ||
uppercase: true, | ||
lowercase: true, | ||
numbers: true, | ||
special: false, | ||
}; | ||
const state = await this.passwordGenerationService.generatePassword(passwordOptions); | ||
const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); | ||
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, 'sha256'); | ||
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); | ||
|
||
await this.storageService.save(ConstantsService.ssoCodeVerifierKey, codeVerifier); | ||
await this.storageService.save(ConstantsService.ssoStateKey, state); | ||
|
||
const authorizeUrl = this.apiService.identityBaseUrl + '/connect/authorize?' + | ||
'client_id=web&redirect_uri=' + encodeURIComponent(this.redirectUri) + '&' + | ||
'response_type=code&scope=api offline_access&' + | ||
'state=' + state + '&code_challenge=' + codeChallenge + '&' + | ||
'code_challenge_method=S256&response_mode=query&' + | ||
'domain_hint=' + encodeURIComponent(this.identifier); | ||
this.platformUtilsService.launchUri(authorizeUrl, { sameWindow: true }); | ||
} | ||
|
||
private async logIn(code: string, codeVerifier: string) { | ||
this.loggingIn = true; | ||
try { | ||
this.formPromise = this.authService.logInSso(code, codeVerifier, this.redirectUri); | ||
const response = await this.formPromise; | ||
if (response.twoFactor) { | ||
this.platformUtilsService.eventTrack('SSO Logged In To Two-step'); | ||
if (this.onSuccessfulLoginTwoFactorNavigate != null) { | ||
this.onSuccessfulLoginTwoFactorNavigate(); | ||
} else { | ||
this.router.navigate([this.twoFactorRoute], { | ||
queryParams: { | ||
resetMasterPassword: response.resetMasterPassword, | ||
}, | ||
}); | ||
} | ||
} else if (response.resetMasterPassword) { | ||
this.platformUtilsService.eventTrack('SSO - routing to complete registration'); | ||
if (this.onSuccessfulLoginChangePasswordNavigate != null) { | ||
this.onSuccessfulLoginChangePasswordNavigate(); | ||
} else { | ||
this.router.navigate([this.changePasswordRoute]); | ||
} | ||
} else { | ||
const disableFavicon = await this.storageService.get<boolean>(ConstantsService.disableFaviconKey); | ||
await this.stateService.save(ConstantsService.disableFaviconKey, !!disableFavicon); | ||
if (this.onSuccessfulLogin != null) { | ||
this.onSuccessfulLogin(); | ||
} | ||
this.platformUtilsService.eventTrack('SSO Logged In'); | ||
if (this.onSuccessfulLoginNavigate != null) { | ||
this.onSuccessfulLoginNavigate(); | ||
} else { | ||
this.router.navigate([this.successRoute]); | ||
} | ||
} | ||
} catch { } | ||
this.loggingIn = false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export class SetPasswordRequest { | ||
newMasterPasswordHash: string; | ||
key: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters