Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 4 additions & 2 deletions apps/36-blocks-widget/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { ChangeDetectionStrategy, Component, effect, inject, OnDestroy, OnInit }
import { CommonModule } from '@angular/common';
import { environment } from '../environments/environment';
import { BaseComponent } from '@proxy/ui/base-component';
import { WidgetTheme, PublicScriptType, WidgetConfig, PROXY_DOM_ID } from '@proxy/constant';
import { WidgetTheme, PublicScriptType, WidgetConfig, PROXY_DOM_ID, WidgetAuthType } from '@proxy/constant';
import { WidgetThemeService } from './otp/service/widget-theme.service';

const REFERENCE_ID = '4512365o17835822266a4f4e12e0cd1';
const THEME: WidgetTheme = WidgetTheme.System;
const TYPE: PublicScriptType = PublicScriptType.Authorization;
/** Classic login vs OAuth 2.0 — set to `WidgetAuthType.Auth2` to test authorize. */
const AUTH_TYPE: WidgetAuthType = WidgetAuthType.Auth2;
const OPEN_EDIT_PROFILE = true;
const AUTH_TOKEN = '';

Expand Down Expand Up @@ -54,7 +56,7 @@ export class AppComponent extends BaseComponent implements OnInit, OnDestroy {
if (!environment.production) {
const widgetConfig: WidgetConfig = {
referenceId: REFERENCE_ID, // Always pass referenceId

// authType: AUTH_TYPE,
target: '_self', // '_blank' | '_self'
success: (data: unknown) => {
console.log('Success response:', data);
Expand Down
3 changes: 2 additions & 1 deletion apps/36-blocks-widget/src/app/init-verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ProxyAuthWidgetComponent } from './otp/widget/widget.component';
import { omit } from 'lodash-es';
import { PROXY_DOM_ID, PublicScriptType, WidgetEvent } from '@proxy/constant';

export const RESERVED_KEYS = ['referenceId', 'target', 'style', 'success', 'failure'];
export const RESERVED_KEYS = ['referenceId', 'target', 'style', 'success', 'failure', 'authType'];

declare global {
interface Window {
Expand Down Expand Up @@ -78,6 +78,7 @@ if (!window.initVerification) {
WithProperties<ProxyAuthWidgetComponent>;
widgetElement.referenceId = config?.referenceId;
widgetElement.type = config?.type;
widgetElement.authType = config?.authType || 'authorization';
widgetElement.authToken = config?.authToken;
widgetElement.showCompanyDetails = config?.showCompanyDetails;
widgetElement.userToken = config?.userToken;
Expand Down
31 changes: 31 additions & 0 deletions apps/36-blocks-widget/src/app/otp/service/otp.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,37 @@ export class OtpService {
return this.http.post<BaseResponse<IWidgetResponse, IGetWidgetData>>(url, payload ?? {}, this.options);
}

/** OAuth 2.0 authorize validation — passes through all query params from the host page URL. */
public getOauthAuthorize(params: Record<string, string>): Observable<BaseResponse<any, void>> {
return this.http.get<BaseResponse<any, void>>(
otpVerificationUrls.oauthAuthorize(this.baseUrl),
params,
this.options
);
}

/**
* OAuth 2.0 consent decision. Sends `proxy_auth_token` as a header (same as other c-user APIs).
* Response includes the destination URL for the single browser navigation.
*/
public postOauthAuthorizeDecision(
payload: Record<string, any>,
proxyAuthToken: string
): Observable<BaseResponse<any, void>> {
const options = {
...this.options,
headers: {
...this.options.headers,
proxy_auth_token: proxyAuthToken,
},
};
return this.http.post<BaseResponse<any, void>>(
otpVerificationUrls.oauthAuthorizeDecision(this.baseUrl),
payload,
options
);
}

public sendOtp(request: ISendOtpReq): Observable<OtpResModel> {
this.setOtpRequestHeaders(request);
const body = { identifier: request.identifier ?? request.mobile };
Expand Down
2 changes: 2 additions & 0 deletions apps/36-blocks-widget/src/app/otp/service/urls/otp-urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { createUrl } from '@proxy/service';

export const otpVerificationUrls = {
getWidgetData: (baseUrl) => createUrl(baseUrl, ':referenceId/widget'),
oauthAuthorize: (baseUrl) => createUrl(baseUrl, 'oauth/authorize'),
oauthAuthorizeDecision: (baseUrl) => createUrl(baseUrl, 'oauth/authorize/decision'),
sendOtp: (baseUrl) => createUrl(baseUrl, 'otp/send'),
retryOtp: (baseUrl) => createUrl(baseUrl, 'otp/retry'),
verifyOtpV2: (baseUrl) => createUrl(baseUrl, 'otp/verify'),
Expand Down
107 changes: 102 additions & 5 deletions apps/36-blocks-widget/src/app/otp/widget/widget.component.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@if (viewMode() === PublicScriptType.Authorization) {
@if (viewMode() === PublicScriptType.Authorization && !showOauthConsent() && !showOauthError()) {
<!-- Button container for Authorization mode - buttons injected here -->
<div #buttonContainer [class.dark]="isDarkTheme()" class="contents"></div>
}
Expand Down Expand Up @@ -38,14 +38,109 @@
</div>
}

@if (showRegistration() || showLogin() || showForgotPassword()) {
@if (showRegistration() || showLogin() || showForgotPassword() || showOauthConsent() || showOauthError()) {
<div #dialogPortal [class.dark]="isDarkTheme()">
<div
class="w-dialog-backdrop"
(click)="showForgotPassword() ? closeForgotPasswordDialog() : closeOverlayDialog()"
(click)="
showOauthConsent() || showOauthError()
? null
: showForgotPassword()
? closeForgotPasswordDialog()
: closeOverlayDialog()
"
aria-hidden="true"
></div>

@if (showOauthError()) {
<div
role="dialog"
aria-labelledby="oauth-error-title"
aria-modal="true"
class="w-dialog-panel w-dialog-panel--md"
>
<div class="w-dialog-header">
<h2 id="oauth-error-title" class="w-dialog-title flex-1">Authorization failed</h2>
</div>
<div class="w-dialog-body">
<p class="w-section-subtitle mt-0">
{{ oauthErrorMessage() }}
</p>
</div>
</div>
}

@if (showOauthConsent()) {
<div
role="dialog"
aria-labelledby="oauth-consent-title"
aria-modal="true"
class="w-dialog-panel w-dialog-panel--md"
>
<div class="w-dialog-header">
<h2 id="oauth-consent-title" class="w-dialog-title flex-1">
{{ oauthAuthorizeData?.client_name || 'Application' }} wants to access your account
</h2>
</div>
<div class="w-dialog-body space-y-4">
<p class="w-section-subtitle mt-0">
This will allow {{ oauthAuthorizeData?.client_name || 'this application' }} to:
</p>
@if (oauthConsentScopeLabels().length) {
<ul class="w-body-list">
@for (label of oauthConsentScopeLabels(); track label) {
<li
class="text-sm font-normal"
[style.color]="isDarkTheme() ? '#d1d5db' : '#374151'"
[style.font-size.px]="14"
>
{{ label }}
</li>
}
</ul>
} @else {
<p class="w-section-subtitle mt-0">This app is not requesting any specific permissions.</p>
}
@if (oauthConsentError()) {
<p role="alert" class="w-field-error">{{ oauthConsentError() }}</p>
}
</div>
<div class="w-dialog-footer">
<button
type="button"
class="w-btn-secondary"
[disabled]="oauthConsentLoading()"
(click)="submitOauthConsent(false)"
>
Deny
</button>
<button
type="button"
class="w-btn-primary"
[disabled]="oauthConsentLoading() || !oauthAuthorizeData"
(click)="submitOauthConsent(true)"
>
@if (oauthConsentLoading()) {
<svg class="w-spinner" aria-hidden="true" fill="none" viewBox="0 0 24 24">
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
/>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8z" />
</svg>
Please wait...
} @else {
Allow
}
</button>
</div>
</div>
}

@if (showForgotPassword()) {
<div
role="dialog"
Expand Down Expand Up @@ -248,7 +343,7 @@ <h2 id="fp-dialog-title" class="w-dialog-title flex-1">
</div>
}

@if (!showForgotPassword()) {
@if (!showForgotPassword() && !showOauthConsent() && !showOauthError()) {
<div role="dialog" aria-modal="true" class="w-dialog-panel" [class.w-dialog-panel--lg]="showRegistration()">
@if (isOtpLoading()) {
<proxy-progress-bar></proxy-progress-bar>
Expand Down Expand Up @@ -293,7 +388,9 @@ <h2 class="w-dialog-title">{{ showRegistration() ? 'Register' : 'Login' }}</h2>
<proxy-login
[loginServiceData]="loginWidgetData"
[theme]="theme"
[isCreateAccountTextAppended]="isCreateAccountTextAppended"
[isCreateAccountTextAppended]="
isCreateAccountTextAppended || authType === WidgetAuthType.Auth2
"
(togglePopUp)="closeOverlayDialog()"
(closePopUp)="closeOverlayDialog()"
(openPopUp)="setShowRegistrationFromLogin($event)"
Expand Down
Loading
Loading