Skip to content
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

feat: CXSPA-6689 one time password for login #18752

Merged
merged 106 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
f7d7cd3
CXSPA-6672: add otp login
niehuayang Apr 12, 2024
7076eeb
CXSPA-6689: add otp login component
i333055 Apr 12, 2024
950b88f
Merge branch 'feature/CXSPA-6672' into feat/CXSPA-6689
i333055 Apr 12, 2024
1d99f57
CXSPA-6689: code refined
i333055 Apr 12, 2024
24bff84
refine code
niehuayang Apr 12, 2024
04ce6b5
Merge branch 'feat/CXSPA-6689' into feature/CXSPA-6672
niehuayang Apr 12, 2024
e3f78a6
CXSPA-6689
i333055 Apr 12, 2024
8b46854
test
niehuayang Apr 12, 2024
5a91d4e
Merge remote-tracking branch 'origin/feat/CXSPA-6689' into feature/CX…
niehuayang Apr 12, 2024
780f912
add route
niehuayang Apr 15, 2024
e275480
CXSPA-6689: add dialog for otp token
i333055 Apr 16, 2024
b35b343
refine code
niehuayang Apr 17, 2024
5fc5dac
CXSPA-6689: add new method for otp login
i333055 Apr 17, 2024
1657f54
Merge branch 'feature/CXSPA-6672' into feat/CXSPA-6689
i333055 Apr 17, 2024
ab20750
CXSPA-6689: bug fixed
i333055 Apr 17, 2024
4e52f22
add ut
niehuayang Apr 17, 2024
56e83ad
Merge branch 'feature/CXSPA-6672' into feat/CXSPA-6689
niehuayang Apr 17, 2024
cdb17c5
add otp cms component
niehuayang Apr 18, 2024
b30b17c
remove env var
niehuayang Apr 18, 2024
6ba6f02
refine code
niehuayang Apr 18, 2024
b3ccbfc
CXSPA-6689: code refined
i333055 Apr 18, 2024
131b5d6
add ut
niehuayang Apr 19, 2024
832f567
Merge branch 'feat/CXSPA-6689' of https://github.com/SAP/spartacus in…
niehuayang Apr 19, 2024
97a40db
CXSPA-6689: add unit tests
i333055 Apr 19, 2024
bfc5165
add e2e test
niehuayang Apr 22, 2024
39294e3
Merge branch 'feat/CXSPA-6689' of github.com:SAP/spartacus into feat/…
i333055 Apr 22, 2024
12d3362
CXSPA-6689: code refined
i333055 Apr 22, 2024
a8cc45b
CXSPA-6689: add unit test
i333055 Apr 22, 2024
85a6d4e
Merge branch 'develop' into feat/CXSPA-6689
i333055 Apr 22, 2024
d11f383
CXSPA: sonar fixed
i333055 Apr 22, 2024
21d9ef3
Merge branch 'feat/CXSPA-6689' of github.com:SAP/spartacus into feat/…
i333055 Apr 22, 2024
2e7db49
Merge branch 'develop' into feat/CXSPA-6689
i333055 Apr 23, 2024
cb9b70b
CXSPA-6689: add unit tests
i333055 Apr 23, 2024
982ca56
Merge branch 'feat/CXSPA-6689' of github.com:SAP/spartacus into feat/…
i333055 Apr 23, 2024
f4ef714
CXSPA-6689: code refined
i333055 Apr 23, 2024
6d0b41d
Merge branch 'develop' into feat/CXSPA-6689
i333055 Apr 23, 2024
05b2041
CXSPA-6689: bug fixed
i333055 Apr 23, 2024
01378e0
Merge branch 'feat/CXSPA-6689' of github.com:SAP/spartacus into feat/…
i333055 Apr 23, 2024
8ef7f26
CXSPA-6689: code refined
i333055 Apr 23, 2024
527ed11
Merge branch 'develop' into feat/CXSPA-6689
i333055 Apr 23, 2024
81cf0b0
Merge branch 'develop' into feat/CXSPA-6689
i333055 Apr 23, 2024
0b57646
refine lighthouse check
niehuayang Apr 24, 2024
9ad0965
refine code
niehuayang Apr 24, 2024
33eb70f
CXSPA-6689: code refined
i333055 Apr 24, 2024
9543ef0
Merge branch 'feat/CXSPA-6689' of github.com:SAP/spartacus into feat/…
i333055 Apr 24, 2024
02aa8c7
Merge branch 'develop' into feat/CXSPA-6689
i333055 Apr 24, 2024
5421af9
add e2e test
niehuayang Apr 24, 2024
939f6b9
Merge branch 'feat/CXSPA-6689' of https://github.com/SAP/spartacus in…
niehuayang Apr 24, 2024
de1c020
add accessibility e2e test
niehuayang Apr 25, 2024
e697cae
remove unused code
niehuayang Apr 25, 2024
8615672
CXSPA-6689: bug fixed
i333055 Apr 25, 2024
27df718
Merge branch 'feat/CXSPA-6689' of github.com:SAP/spartacus into feat/…
i333055 Apr 25, 2024
ec2a0ad
CXSPA-6689: bug fixed
i333055 Apr 25, 2024
005469b
refine e2e
niehuayang Apr 25, 2024
d547263
fix comments
niehuayang Apr 25, 2024
f685db2
CXSPA: code refined for code review
i333055 Apr 25, 2024
c07fbb9
rename LoginForm model to VerificationTokenCreation model
niehuayang Apr 25, 2024
c089a96
Merge branch 'feat/CXSPA-6689' of https://github.com/SAP/spartacus in…
niehuayang Apr 25, 2024
c4e071c
CXSPA-6689: code refined
i333055 Apr 25, 2024
b268794
Merge branch 'feat/CXSPA-6689' of github.com:SAP/spartacus into feat/…
i333055 Apr 25, 2024
121b737
Merge branch 'develop' into feat/CXSPA-6689
i333055 Apr 28, 2024
30a1975
CXSPA-6689: lint fix
i333055 Apr 28, 2024
658947b
Merge branch 'feat/CXSPA-6689' of github.com:SAP/spartacus into feat/…
i333055 Apr 28, 2024
346e479
Merge branch 'develop' into feat/CXSPA-6689
i333055 Apr 29, 2024
fb03b15
move translation
niehuayang Apr 29, 2024
e5add12
prettier:fix
niehuayang Apr 29, 2024
45b81cd
Trigger Build
niehuayang Apr 29, 2024
0a3bb67
CXSPA-6689: color fixed
i333055 Apr 29, 2024
f3807f5
CXSPA-6689: code refined
i333055 Apr 30, 2024
aaeaa53
Merge branch 'develop' into feat/CXSPA-6689
i333055 Apr 30, 2024
81fc55c
CXSPA-6689: translation fixed
i333055 Apr 30, 2024
fec6ccd
Merge branch 'feat/CXSPA-6689' of github.com:SAP/spartacus into feat/…
i333055 Apr 30, 2024
7361b4e
CXSPA-6689: code refined
i333055 Apr 30, 2024
ed371cf
revert translation change
niehuayang Apr 30, 2024
5a2990c
Merge branch 'feat/CXSPA-6689' of https://github.com/SAP/spartacus in…
niehuayang Apr 30, 2024
94d36ce
revert translation change
niehuayang Apr 30, 2024
44cfba8
prettier:fix
niehuayang Apr 30, 2024
5e18938
Merge branch 'develop' into feat/CXSPA-6689
i333055 Apr 30, 2024
26ee0a6
Merge branch 'develop' into feat/CXSPA-6689
i333055 May 2, 2024
bbdaead
Merge branch 'develop' into feat/CXSPA-6689
i333055 May 2, 2024
0b989d4
CXSPA-6689: css fixed
i333055 May 7, 2024
af1f6f1
Merge branch 'develop' into feat/CXSPA-6689
i333055 May 7, 2024
c05f32f
Merge branch 'develop' into feat/CXSPA-6689
i333055 May 8, 2024
062d916
Merge branch 'develop' into feat/CXSPA-6689
niehuayang May 13, 2024
d7994f2
enable traceability for e2e accessbility test
niehuayang May 13, 2024
130641f
Merge branch 'feat/CXSPA-6689' of https://github.com/SAP/spartacus in…
niehuayang May 13, 2024
e71aa33
Merge branch 'develop' into feat/CXSPA-6689
i333055 May 13, 2024
7ff3258
Merge branch 'develop' into feat/CXSPA-6689
i333055 May 14, 2024
86935ba
CXSPA-6689: code refined after code review
i333055 May 14, 2024
1052aca
CXSPA-6689: unit failed fixed
i333055 May 14, 2024
00e67c9
CXSPA-6689: code refined
i333055 May 14, 2024
6751b62
fix comments
niehuayang May 15, 2024
74bc7ee
CXSPA-6689: code refined
i333055 May 15, 2024
0241b49
Merge branch 'develop' into feat/CXSPA-6689
i333055 May 15, 2024
3d910c7
CXSPA-6689: code refined
i333055 May 15, 2024
5371226
CXSPA-6689: code refined
i333055 May 15, 2024
8eb08f8
refine code
niehuayang May 15, 2024
8820e26
CXSPA-6689: lint fixed
i333055 May 15, 2024
c4809f9
code refine
niehuayang May 15, 2024
305075d
Merge branch 'feat/CXSPA-6689' of https://github.com/SAP/spartacus in…
niehuayang May 15, 2024
54d297a
refine code
niehuayang May 15, 2024
65da10a
CXSPA-6689: css fixed
i333055 May 15, 2024
3dae31a
Merge branch 'feat/CXSPA-6689' of github.com:SAP/spartacus into feat/…
i333055 May 15, 2024
0a952d6
CXSPA-6689: accessibility fixed
i333055 May 15, 2024
064ef62
CXSPA-6689: accessibility fixed
i333055 May 15, 2024
c3fcd24
Merge branch 'develop' into feat/CXSPA-6689
niehuayang May 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion feature-libs/user/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ $skipComponentStyles: () !default;
$selectors: cx-address-book, cx-address-form, cx-suggested-addresses-dialog,
cx-login, cx-login-form, cx-register, cx-reset-password, cx-close-account,
cx-close-account-modal, cx-my-account-v2-profile, cx-my-account-v2-email,
cx-my-account-v2-password !default;
cx-my-account-v2-password, cx-otp-login-form, cx-verification-token-form,
cx-verification-token-dialog !default;

@each $selector in $selectors {
#{$selector} {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,26 @@
},
"wrongEmailFormat": "This is not a valid email format."
},
"verificationTokenForm": {
"createVerificationToken": "Verification code sent to {{target}}. Please check and enter the code.",
"sendRateLime": "in {{waitTime}} seconds",
"resend": "Resend",
"verificationCode": {
"label": "Verification Code",
"placeholder": "Enter Verification Code"
},
"noReceiveCode": "Didn't receive the code?",
"verify": "Verify",
"back": "Back"
},
"verificationTokenDialog": {
"title": "Didn’t receive the code",
"noReceiveCode": "If you don't receive the code, the reasons might be:",
"contentLine1": "1. The email hasn't come yet.",
"contentLine2": "2. The received email has been treated as junk email.",
"contentLine3": "3. Either the email address or the password you entered is incorrect.",
"close": "Close"
},
"miniLogin": {
"userGreeting": "Hi, {{name}}",
"signInRegister": "Sign In / Register"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,11 @@ export const userAccountTranslations: TranslationResources = {
};

export const userAccountTranslationChunksConfig: TranslationChunksConfig = {
userAccount: ['loginForm', 'miniLogin', 'myAccountV2User'],
userAccount: [
'loginForm',
'verificationTokenForm',
'verificationTokenDialog',
'miniLogin',
'myAccountV2User',
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* SPDX-FileCopyrightText: 2024 SAP Spartacus team <[email protected]>
*
* SPDX-License-Identifier: Apache-2.0
*/

export * from './otp-login-form.component';
export * from './otp-login-form.module';
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<cx-spinner class="overlay" *ngIf="isUpdating$ | async"></cx-spinner>

<form (ngSubmit)="onSubmit()" [formGroup]="form">
<!-- TODO: (CXSPA-5953) Remove feature flags next major -->
<p *cxFeature="'a11yRequiredAsterisks'" class="form-legend">
{{ 'formLegend.required' | cxTranslate }}
</p>
<label>
<span class="label-content">
{{ 'loginForm.emailAddress.label' | cxTranslate }}
<ng-template [ngTemplateOutlet]="requiredAsterisk"></ng-template>
</span>
<input
required="true"
type="email"
class="form-control"
formControlName="userId"
placeholder="{{ 'loginForm.emailAddress.placeholder' | cxTranslate }}"
/>
<cx-form-errors [control]="form.get('userId')"></cx-form-errors>
</label>

<label>
<span class="label-content">
{{ 'loginForm.password.label' | cxTranslate }}
<ng-template [ngTemplateOutlet]="requiredAsterisk"></ng-template>
</span>
<input
required="true"
type="password"
class="form-control"
placeholder="{{ 'loginForm.password.placeholder' | cxTranslate }}"
formControlName="password"
[attr.aria-label]="'loginForm.password.placeholder' | cxTranslate"
cxPasswordVisibilitySwitch
/>
<cx-form-errors [control]="form.get('password')"></cx-form-errors>
</label>

<a [routerLink]="{ cxRoute: 'forgotPassword' } | cxUrl" class="btn-link">
{{ 'loginForm.forgotPassword' | cxTranslate }}
</a>

<button
type="submit"
class="btn btn-block btn-primary"
[disabled]="form.disabled"
>
{{ 'loginForm.signIn' | cxTranslate }}
</button>
</form>

<ng-template #requiredAsterisk>
<abbr
*cxFeature="'a11yRequiredAsterisks'"
class="text-decoration-none"
title="{{ 'common.required' | cxTranslate }}"
>*</abbr
>
</ng-template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import { DebugElement, Pipe, PipeTransform } from '@angular/core';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { I18nTestingModule, RoutingService, WindowRef } from '@spartacus/core';
import { FormErrorsModule, SpinnerModule } from '@spartacus/storefront';
import { VerificationTokenService } from '@spartacus/user/account/core';
import {
VerificationToken,
VerificationTokenCreation,
VerificationTokenFacade,
} from '@spartacus/user/account/root';
import { of } from 'rxjs';
import { OneTimePasswordLoginFormComponent } from './otp-login-form.component';
import createSpy = jasmine.createSpy;

const verificationTokenCreation: VerificationTokenCreation = {
purpose: 'LOGIN',
loginId: '[email protected]',
password: '1234',
};

const verificationToken: VerificationToken = {
expiresIn: '300',
tokenId: 'mockTokenId',
};

class MockWinRef {
get nativeWindow(): Window {
return {} as Window;
}
}

class MockVerificationTokenService
implements Partial<VerificationTokenService>
{
createVerificationToken = createSpy().and.callFake(() =>
of(verificationToken)
);
}

class MockRoutingService implements Partial<RoutingService> {
go = () => Promise.resolve(true);
}

@Pipe({
name: 'cxUrl',
})
class MockUrlPipe implements PipeTransform {
transform() {}
}

describe('OneTimePasswordLoginFormComponent', () => {
let component: OneTimePasswordLoginFormComponent;
let fixture: ComponentFixture<OneTimePasswordLoginFormComponent>;
let el: DebugElement;
let service: VerificationTokenFacade;
let winRef: WindowRef;

beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
ReactiveFormsModule,
RouterTestingModule,
I18nTestingModule,
FormErrorsModule,
SpinnerModule,
],
declarations: [OneTimePasswordLoginFormComponent, MockUrlPipe],
providers: [
{
provide: VerificationTokenFacade,
useClass: MockVerificationTokenService,
},
{ provide: WindowRef, useClass: MockWinRef },
{ provide: RoutingService, useClass: MockRoutingService },
],
}).compileComponents();
})
);

beforeEach(() => {
winRef = TestBed.inject(WindowRef);
fixture = TestBed.createComponent(OneTimePasswordLoginFormComponent);
service = TestBed.inject(VerificationTokenFacade);
component = fixture.componentInstance;
el = fixture.debugElement;
fixture.detectChanges();
});

it('should create component', () => {
expect(component).toBeTruthy();
});

describe('create OTP', () => {
it('should not patch user id', () => {
component.isUpdating$.subscribe().unsubscribe();
expect(component.form.value.userId).toEqual('');
});

it('should patch user id', () => {
spyOnProperty(winRef, 'nativeWindow', 'get').and.returnValue({
history: { state: { newUid: verificationTokenCreation.loginId } },
} as Window);
component.isUpdating$.subscribe().unsubscribe();
expect(component.form.value.userId).toEqual(
verificationTokenCreation.loginId
);
});

describe('success', () => {
beforeEach(() => {
component.form.setValue({
userId: verificationTokenCreation.loginId,
password: verificationTokenCreation.password,
});
});

it('should request email', () => {
component.onSubmit();
expect(service.createVerificationToken).toHaveBeenCalledWith(
verificationTokenCreation
);
});

it('should reset the form', () => {
spyOn(component.form, 'reset').and.stub();
component.onSubmit();
expect(component.form.reset).toHaveBeenCalled();
});
});

describe('error', () => {
beforeEach(() => {
component.form.setValue({
userId: 'invalid',
password: '123',
});
});

it('should not create OTP', () => {
component.onSubmit();
expect(service.createVerificationToken).not.toHaveBeenCalled();
});

it('should not reset the form', () => {
spyOn(component.form, 'reset').and.stub();
component.onSubmit();
expect(component.form.reset).not.toHaveBeenCalled();
});
});
});

describe('busy', () => {
it('should disable the submit button when form is disabled', () => {
component.form.disable();
fixture.detectChanges();
const submitBtn: HTMLButtonElement = el.query(
By.css('button')
).nativeElement;
expect(submitBtn.disabled).toBeTruthy();
});

it('should show the spinner', () => {
// @ts-ignore
component.busy$.next(true);
fixture.detectChanges();
expect(el.query(By.css('cx-spinner'))).toBeTruthy();
});
});

describe('idle', () => {
it('should enable the submit button', () => {
component.form.enable();
fixture.detectChanges();
const submitBtn = el.query(By.css('button'));
expect(submitBtn.nativeElement.disabled).toBeFalsy();
});

it('should not show the spinner', () => {
// @ts-ignore
component.isUpdating$.next(false);
fixture.detectChanges();
expect(el.query(By.css('cx-spinner'))).toBeNull();
});
});

describe('Form Interactions', () => {
it('should call onSubmit() method on submit', () => {
const request = spyOn(component, 'onSubmit');
const form = el.query(By.css('form'));
form.triggerEventHandler('submit', null);
expect(request).toHaveBeenCalled();
});

it('should call the service method on submit', () => {
component.form.setValue({
userId: verificationTokenCreation.loginId,
password: verificationTokenCreation.password,
});
component.onSubmit();
expect(service.createVerificationToken).toHaveBeenCalled();
});
});
});
Loading
Loading