-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[PM-8221] Route user to email OTP entry [clients] #12811
base: main
Are you sure you want to change the base?
Changes from 31 commits
2877a71
b741e45
89cba4f
d3d5ee6
1d98fa8
0504513
b377236
1eb8c5a
3db8357
4188e4a
c71bf55
6b637c7
03fbd03
818e0fd
5d6fa5c
a5b295a
6e9337a
bb49296
c2d4ee2
fc7754b
5baac33
3fda156
0c5a559
426a3dd
ef37b85
2eb175c
d4b6682
b103f99
d90b2bd
76588e0
25d3c6e
c9a00c6
d1b0175
ce10ca3
9a8c143
ab91188
491f29a
6bd2558
96a4f03
100103d
052d9d8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice test suite! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { TestBed } from "@angular/core/testing"; | ||
import { Router } from "@angular/router"; | ||
import { RouterTestingModule } from "@angular/router/testing"; | ||
import { MockProxy, mock } from "jest-mock-extended"; | ||
import { BehaviorSubject } from "rxjs"; | ||
|
||
import { EmptyComponent } from "@bitwarden/angular/platform/guard/feature-flag.guard.spec"; | ||
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; | ||
import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; | ||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; | ||
|
||
import { newDeviceVerificationGuard } from "./new-device-verification.guard"; | ||
|
||
describe("NewDeviceVerificationGuard", () => { | ||
const setup = (authType: AuthenticationType | null) => { | ||
const loginStrategyService: MockProxy<LoginStrategyServiceAbstraction> = | ||
mock<LoginStrategyServiceAbstraction>(); | ||
const currentAuthTypeSubject = new BehaviorSubject<AuthenticationType | null>(authType); | ||
loginStrategyService.currentAuthType$ = currentAuthTypeSubject; | ||
|
||
const logService: MockProxy<LogService> = mock<LogService>(); | ||
|
||
const testBed = TestBed.configureTestingModule({ | ||
imports: [ | ||
RouterTestingModule.withRoutes([ | ||
{ path: "", component: EmptyComponent }, | ||
{ | ||
path: "device-verification", | ||
component: EmptyComponent, | ||
canActivate: [newDeviceVerificationGuard()], | ||
}, | ||
{ path: "login", component: EmptyComponent }, | ||
]), | ||
], | ||
providers: [ | ||
{ provide: LoginStrategyServiceAbstraction, useValue: loginStrategyService }, | ||
{ provide: LogService, useValue: logService }, | ||
], | ||
}); | ||
|
||
return { | ||
router: testBed.inject(Router), | ||
logService, | ||
}; | ||
}; | ||
|
||
it("creates the guard", () => { | ||
const { router } = setup(AuthenticationType.Password); | ||
expect(router).toBeTruthy(); | ||
}); | ||
|
||
it("allows access with an active login session", async () => { | ||
const { router } = setup(AuthenticationType.Password); | ||
|
||
await router.navigate(["device-verification"]); | ||
expect(router.url).toBe("/device-verification"); | ||
}); | ||
|
||
it("redirects to login with no active session", async () => { | ||
const { router, logService } = setup(null); | ||
|
||
await router.navigate(["device-verification"]); | ||
expect(router.url).toBe("/login"); | ||
expect(logService.error).toHaveBeenCalledWith("No active login session found."); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { inject } from "@angular/core"; | ||
import { CanActivateFn, Router } from "@angular/router"; | ||
import { firstValueFrom } from "rxjs"; | ||
|
||
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; | ||
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; | ||
|
||
/** | ||
* Guard that ensures there is an active login session before allowing access | ||
* to the new device verification route. | ||
* If not, redirects to login. | ||
*/ | ||
export function newDeviceVerificationGuard(): CanActivateFn { | ||
return async () => { | ||
const loginStrategyService = inject(LoginStrategyServiceAbstraction); | ||
const logService = inject(LogService); | ||
const router = inject(Router); | ||
|
||
// Check if we have a valid login session | ||
const authType = await firstValueFrom(loginStrategyService.currentAuthType$); | ||
if (authType === null) { | ||
logService.error("No active login session found."); | ||
return router.createUrlTree(["/login"]); | ||
} | ||
|
||
return true; | ||
}; | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. โ : is this still necessary after we updated the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do, since we're subscribing to |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { svgIcon } from "@bitwarden/components"; | ||
|
||
export const DeviceVerificationIcon = svgIcon` | ||
<svg viewBox="0 0 98 95" fill="none" xmlns="http://www.w3.org/2000/svg"> | ||
<path class="tw-stroke-art-primary" d="M12.1759 27.7453L2.54349 34.9329C1.57215 35.6577 1 36.7986 1 38.0105V89.6281C1 91.7489 2.71922 93.4681 4.84 93.4681H93.16C95.2808 93.4681 97 91.7489 97 89.6281V38.0276C97 36.806 96.4188 35.6574 95.4347 34.9338L85.6576 27.7453M61.8791 10.2622L50.9367 2.2168C49.5753 1.21588 47.7197 1.22245 46.3655 2.23297L35.6054 10.2622" stroke-width="1.92"/> | ||
<path class="tw-stroke-art-primary" d="M85.7661 45.4682V12.1542C85.7661 11.0938 84.9064 10.2342 83.8461 10.2342H14.1541C13.0937 10.2342 12.2341 11.0938 12.2341 12.1542V45.4682" stroke-width="1.92" stroke-linecap="round"/> | ||
<path class="tw-stroke-art-primary" d="M95.7335 92.1003L62.3151 61.2912C61.2514 60.3106 59.8576 59.7661 58.4109 59.7661H38.043C36.5571 59.7661 35.1286 60.3404 34.0562 61.3689L2.01148 92.1003" stroke-width="1.92"/> | ||
<line class="tw-stroke-art-primary" x1="96.157" y1="39.125" x2="61.0395" y2="60.0979" stroke-width="1.92" stroke-linecap="round"/> | ||
<path class="tw-stroke-art-primary" d="M1.84229 39.1248L36.673 59.7488" stroke-width="1.92" stroke-linecap="round"/> | ||
<rect class="tw-stroke-art-accent" x="23.0046" y="25.5344" width="51.925" height="17.4487" rx="8.72434" stroke-width="0.96"/> | ||
<circle class="tw-fill-art-accent" cx="30.2299" cy="34.2588" r="2.24846"/> | ||
<circle class="tw-fill-art-accent" cx="45.2196" cy="34.2587" r="2.24846"/> | ||
<circle class="tw-fill-art-accent" cx="60.2094" cy="34.2587" r="2.24846"/> | ||
<circle class="tw-fill-art-accent" cx="37.7248" cy="34.2587" r="2.24846"/> | ||
<circle class="tw-fill-art-accent" cx="52.7145" cy="34.2587" r="2.24846"/> | ||
<circle class="tw-fill-art-accent" cx="67.704" cy="34.2587" r="2.24846"/> | ||
</svg> | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
<form [formGroup]="formGroup" [bitSubmit]="submit"> | ||
<bit-form-field> | ||
<bit-label>{{ "verificationCode" | i18n }}</bit-label> | ||
<input | ||
bitInput | ||
type="text" | ||
id="verificationCode" | ||
name="verificationCode" | ||
formControlName="code" | ||
appInputVerbatim | ||
/> | ||
</bit-form-field> | ||
|
||
<button | ||
bitLink | ||
type="button" | ||
linkType="primary" | ||
(click)="resendOTP()" | ||
[disabled]="disableRequestOTP" | ||
> | ||
{{ "resendCode" | i18n }} | ||
</button> | ||
|
||
<div class="tw-flex tw-mt-4"> | ||
<button | ||
bitButton | ||
buttonType="primary" | ||
type="submit" | ||
[block]="true" | ||
[disabled]="formGroup.invalid" | ||
> | ||
{{ "continueLoggingIn" | i18n }} | ||
</button> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ก , since we have a back button or x on the extension, IOS, and Android, I wonder if we should have an explicit cancel button here as well for desktop and web (using the new device verification service to inform the component to show the button or not). Can you run that by product / design? Note: the bitwarden logo does take the user back to login so that might be deemed sufficient, but we do have a cancel on 2FA which is an comparable screen to this one. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
</div> | ||
</form> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ExtensionAnonLayoutWrapperComponent
withExtensionAnonLayoutWrapperData
. We should have a back button for the new page per Figma:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
d1b0175d1b0175