@dereekb/dbx-firebase 13.2.2 → 13.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1378 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, computed, output, ChangeDetectionStrategy, Component, inject, Injectable, signal, effect, Directive, provideAppInitializer, makeEnvironmentProviders } from '@angular/core';
3
+ import { DbxFirebaseLoginComponent, DbxFirebaseAuthService, AbstractDbxFirebaseDocumentStore, firebaseDocumentStoreCreateFunction, firebaseDocumentStoreUpdateFunction, firebaseDocumentStoreDeleteFunction, AbstractDbxFirebaseCollectionStore, DbxFirebaseCollectionStoreDirective, provideDbxFirebaseCollectionStoreDirective, DbxFirebaseDocumentStoreDirective, provideDbxFirebaseDocumentStoreDirective } from '@dereekb/dbx-firebase';
4
+ import * as i1$1 from '@dereekb/dbx-web';
5
+ import { DbxBasicLoadingComponent, DbxErrorComponent, DbxButtonComponent, DbxAvatarComponent, DbxLoadingComponent, DbxButtonSpacerDirective, AbstractDbxSelectionListWrapperDirective, DbxListWrapperComponentImportsModule, provideDbxListViewWrapper, DEFAULT_LIST_WRAPPER_COMPONENT_CONFIGURATION_TEMPLATE, AbstractDbxSelectionListViewDirective, DbxSelectionValueListViewComponentImportsModule, provideDbxListView, DEFAULT_DBX_SELECTION_VALUE_LIST_COMPONENT_CONFIGURATION_TEMPLATE, AbstractDbxValueListViewItemComponent, DbxActionSnackbarErrorDirective, DbxContentPitDirective, DbxDetailBlockComponent, DbxClickToCopyTextComponent, DbxActionConfirmDirective } from '@dereekb/dbx-web';
6
+ import { readableError, SPACE_STRING_SPLIT_JOIN, separateValues } from '@dereekb/util';
7
+ import { DbxInjectionComponent, DBX_INJECTION_COMPONENT_DATA, DbxRouterService, dbxRouteParamReaderInstance, completeOnDestroy, DbxActionDirective, DbxActionEnforceModifiedDirective, DbxActionHandlerDirective, DbxActionButtonDirective, DbxAppAuthRouterService } from '@dereekb/dbx-core';
8
+ import { toSignal } from '@angular/core/rxjs-interop';
9
+ import { HttpClient } from '@angular/common/http';
10
+ import { first, switchMap, of, map, BehaviorSubject, tap } from 'rxjs';
11
+ import * as i1 from '@dereekb/dbx-form';
12
+ import { valueSelectionField, textField, searchableStringChipField, isWebsiteUrlValidator, pickableItemChipField, pickableValueFieldValuesConfigForStaticLabeledValues, AbstractConfigAsyncFormlyFormDirective, DbxFormlyFormComponentImportsModule, dbxFormlyFormComponentProviders, DBX_FORMLY_FORM_COMPONENT_TEMPLATE, DbxActionFormDirective, DbxFormSourceDirective, DbxFormValueChangeDirective } from '@dereekb/dbx-form';
13
+ import { ALL_OIDC_TOKEN_ENDPOINT_AUTH_METHOD_OPTIONS, PRIVATE_KEY_JWT_TOKEN_ENDPOINT_AUTH_METHOD, OIDC_ENTRY_CLIENT_TYPE, OidcModelFunctions, OidcModelFirestoreCollections } from '@dereekb/firebase';
14
+ import { CommonModule } from '@angular/common';
15
+
16
+ /**
17
+ * Presentational component for the OIDC OAuth login interaction.
18
+ *
19
+ * Renders the login UI based on the current state case. Supports ng-content
20
+ * projection to allow apps to provide a custom login view for the `'no_user'` state,
21
+ * falling back to the default `<dbx-firebase-login>` component.
22
+ *
23
+ * @example
24
+ * ```html
25
+ * <dbx-firebase-oauth-login-view [loginStateCase]="'no_user'">
26
+ * <my-custom-login />
27
+ * </dbx-firebase-oauth-login-view>
28
+ * ```
29
+ */
30
+ class DbxFirebaseOAuthLoginViewComponent {
31
+ loginStateCase = input.required(...(ngDevMode ? [{ debugName: "loginStateCase" }] : []));
32
+ error = input(...(ngDevMode ? [undefined, { debugName: "error" }] : []));
33
+ resolvedError = computed(() => {
34
+ const error = this.error();
35
+ return typeof error === 'string' ? readableError('ERROR', error) : error;
36
+ }, ...(ngDevMode ? [{ debugName: "resolvedError" }] : []));
37
+ retryClick = output();
38
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOAuthLoginViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
39
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: DbxFirebaseOAuthLoginViewComponent, isStandalone: true, selector: "dbx-firebase-oauth-login-view", inputs: { loginStateCase: { classPropertyName: "loginStateCase", publicName: "loginStateCase", isSignal: true, isRequired: true, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { retryClick: "retryClick" }, host: { classAttribute: "d-block dbx-firebase-oauth-login-view" }, ngImport: i0, template: `
40
+ <div class="dbx-firebase-oauth-login-view">
41
+ @switch (loginStateCase()) {
42
+ @case ('no_user') {
43
+ <ng-content></ng-content>
44
+ }
45
+ @case ('user') {
46
+ <dbx-basic-loading [loading]="true" text="Signing in..."></dbx-basic-loading>
47
+ }
48
+ @case ('submitting') {
49
+ <dbx-basic-loading [loading]="true" text="Submitting authentication..."></dbx-basic-loading>
50
+ }
51
+ @case ('error') {
52
+ <dbx-button text="Retry" [raised]="true" (buttonClick)="retryClick.emit()"></dbx-button>
53
+ <dbx-error [error]="resolvedError()"></dbx-error>
54
+ }
55
+ }
56
+ </div>
57
+ `, isInline: true, dependencies: [{ kind: "component", type: DbxBasicLoadingComponent, selector: "dbx-basic-loading", inputs: ["diameter", "mode", "color", "text", "linear", "show", "loading", "error"] }, { kind: "component", type: DbxErrorComponent, selector: "dbx-error", inputs: ["error", "iconOnly"], outputs: ["popoverOpened"] }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "mode"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
58
+ }
59
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOAuthLoginViewComponent, decorators: [{
60
+ type: Component,
61
+ args: [{
62
+ selector: 'dbx-firebase-oauth-login-view',
63
+ standalone: true,
64
+ imports: [DbxFirebaseLoginComponent, DbxBasicLoadingComponent, DbxErrorComponent, DbxButtonComponent],
65
+ template: `
66
+ <div class="dbx-firebase-oauth-login-view">
67
+ @switch (loginStateCase()) {
68
+ @case ('no_user') {
69
+ <ng-content></ng-content>
70
+ }
71
+ @case ('user') {
72
+ <dbx-basic-loading [loading]="true" text="Signing in..."></dbx-basic-loading>
73
+ }
74
+ @case ('submitting') {
75
+ <dbx-basic-loading [loading]="true" text="Submitting authentication..."></dbx-basic-loading>
76
+ }
77
+ @case ('error') {
78
+ <dbx-button text="Retry" [raised]="true" (buttonClick)="retryClick.emit()"></dbx-button>
79
+ <dbx-error [error]="resolvedError()"></dbx-error>
80
+ }
81
+ }
82
+ </div>
83
+ `,
84
+ host: {
85
+ class: 'd-block dbx-firebase-oauth-login-view'
86
+ },
87
+ changeDetection: ChangeDetectionStrategy.OnPush
88
+ }]
89
+ }], propDecorators: { loginStateCase: [{ type: i0.Input, args: [{ isSignal: true, alias: "loginStateCase", required: true }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], retryClick: [{ type: i0.Output, args: ["retryClick"] }] } });
90
+
91
+ /**
92
+ * Presentational component for the OIDC OAuth consent screen.
93
+ *
94
+ * Accepts an `OAuthInteractionLoginDetails` input that contains all client and scope
95
+ * information. Renders the client name, logo, client URL, scopes (via `<dbx-injection>`),
96
+ * error/loading states, and approve/deny action buttons.
97
+ *
98
+ * @example
99
+ * ```html
100
+ * <dbx-firebase-oauth-consent-view
101
+ * [details]="loginDetails"
102
+ * [loading]="false"
103
+ * [scopeInjectionConfig]="scopeConfig"
104
+ * (approveClick)="onApprove()"
105
+ * (denyClick)="onDeny()">
106
+ * </dbx-firebase-oauth-consent-view>
107
+ * ```
108
+ */
109
+ class DbxFirebaseOAuthConsentViewComponent {
110
+ details = input(...(ngDevMode ? [undefined, { debugName: "details" }] : []));
111
+ loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
112
+ error = input(...(ngDevMode ? [undefined, { debugName: "error" }] : []));
113
+ scopeInjectionConfig = input.required(...(ngDevMode ? [{ debugName: "scopeInjectionConfig" }] : []));
114
+ clientName = computed(() => this.details()?.client_name ?? '', ...(ngDevMode ? [{ debugName: "clientName" }] : []));
115
+ clientUri = computed(() => this.details()?.client_uri, ...(ngDevMode ? [{ debugName: "clientUri" }] : []));
116
+ logoUri = computed(() => this.details()?.logo_uri, ...(ngDevMode ? [{ debugName: "logoUri" }] : []));
117
+ scopes = computed(() => SPACE_STRING_SPLIT_JOIN.splitStrings(this.details()?.scopes ?? ''), ...(ngDevMode ? [{ debugName: "scopes" }] : []));
118
+ resolvedError = computed(() => {
119
+ const error = this.error();
120
+ return typeof error === 'string' ? readableError('ERROR', error) : error;
121
+ }, ...(ngDevMode ? [{ debugName: "resolvedError" }] : []));
122
+ approveClick = output();
123
+ denyClick = output();
124
+ resolvedScopeInjectionConfig = computed(() => {
125
+ const data = {
126
+ details: this.details(),
127
+ scopes: this.scopes(),
128
+ clientName: this.clientName()
129
+ };
130
+ return { ...this.scopeInjectionConfig(), data };
131
+ }, ...(ngDevMode ? [{ debugName: "resolvedScopeInjectionConfig" }] : []));
132
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOAuthConsentViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
133
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: DbxFirebaseOAuthConsentViewComponent, isStandalone: true, selector: "dbx-firebase-oauth-consent-view", inputs: { details: { classPropertyName: "details", publicName: "details", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, scopeInjectionConfig: { classPropertyName: "scopeInjectionConfig", publicName: "scopeInjectionConfig", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { approveClick: "approveClick", denyClick: "denyClick" }, host: { classAttribute: "d-block dbx-firebase-oauth-consent-view" }, ngImport: i0, template: `
134
+ <div class="dbx-firebase-oauth-consent-view">
135
+ @if (loading()) {
136
+ <dbx-loading [loading]="true" text="Processing..."></dbx-loading>
137
+ } @else {
138
+ <div class="dbx-firebase-oauth-consent-header">
139
+ @if (clientName()) {
140
+ <h2>You're signing in to {{ clientName() }}</h2>
141
+ }
142
+ <div class="dbx-firebase-oauth-consent-header-info dbx-flex">
143
+ <dbx-avatar [avatarUrl]="logoUri()" [avatarStyle]="'square'" avatarIcon="apps"></dbx-avatar>
144
+ <span>
145
+ @if (clientUri()) {
146
+ <a class="dbx-firebase-oauth-consent-client-uri" [href]="clientUri()" target="_blank" rel="noopener noreferrer">{{ clientUri() }}</a>
147
+ }
148
+ </span>
149
+ </div>
150
+ </div>
151
+ <dbx-injection [config]="resolvedScopeInjectionConfig()"></dbx-injection>
152
+ <div class="dbx-pt3 dbx-pb3 dbx-firebase-oauth-consent-actions">
153
+ <dbx-button text="Approve" [raised]="true" color="primary" (buttonClick)="approveClick.emit()"></dbx-button>
154
+ <dbx-button-spacer></dbx-button-spacer>
155
+ <dbx-button text="Deny" [flat]="true" color="warn" (buttonClick)="denyClick.emit()"></dbx-button>
156
+ </div>
157
+ @if (resolvedError()) {
158
+ <dbx-error [error]="resolvedError()"></dbx-error>
159
+ }
160
+ }
161
+ </div>
162
+ `, isInline: true, styles: [".dbx-firebase-oauth-consent-view .dbx-firebase-oauth-consent-header-info{align-items:center;gap:12px}\n"], dependencies: [{ kind: "component", type: DbxInjectionComponent, selector: "dbx-injection, [dbxInjection], [dbx-injection]", inputs: ["config", "template"] }, { kind: "component", type: DbxAvatarComponent, selector: "dbx-avatar", inputs: ["context", "avatarSelector", "avatarUid", "avatarUrl", "avatarKey", "avatarIcon", "avatarStyle", "avatarSize", "avatarHideOnError"] }, { kind: "component", type: DbxLoadingComponent, selector: "dbx-loading", inputs: ["padding", "show", "text", "mode", "color", "diameter", "linear", "loading", "error", "context"] }, { kind: "component", type: DbxErrorComponent, selector: "dbx-error", inputs: ["error", "iconOnly"], outputs: ["popoverOpened"] }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "mode"] }, { kind: "directive", type: DbxButtonSpacerDirective, selector: "dbx-button-spacer,[dbxButtonSpacer]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
163
+ }
164
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOAuthConsentViewComponent, decorators: [{
165
+ type: Component,
166
+ args: [{ selector: 'dbx-firebase-oauth-consent-view', standalone: true, imports: [DbxInjectionComponent, DbxAvatarComponent, DbxLoadingComponent, DbxErrorComponent, DbxButtonComponent, DbxButtonSpacerDirective], template: `
167
+ <div class="dbx-firebase-oauth-consent-view">
168
+ @if (loading()) {
169
+ <dbx-loading [loading]="true" text="Processing..."></dbx-loading>
170
+ } @else {
171
+ <div class="dbx-firebase-oauth-consent-header">
172
+ @if (clientName()) {
173
+ <h2>You're signing in to {{ clientName() }}</h2>
174
+ }
175
+ <div class="dbx-firebase-oauth-consent-header-info dbx-flex">
176
+ <dbx-avatar [avatarUrl]="logoUri()" [avatarStyle]="'square'" avatarIcon="apps"></dbx-avatar>
177
+ <span>
178
+ @if (clientUri()) {
179
+ <a class="dbx-firebase-oauth-consent-client-uri" [href]="clientUri()" target="_blank" rel="noopener noreferrer">{{ clientUri() }}</a>
180
+ }
181
+ </span>
182
+ </div>
183
+ </div>
184
+ <dbx-injection [config]="resolvedScopeInjectionConfig()"></dbx-injection>
185
+ <div class="dbx-pt3 dbx-pb3 dbx-firebase-oauth-consent-actions">
186
+ <dbx-button text="Approve" [raised]="true" color="primary" (buttonClick)="approveClick.emit()"></dbx-button>
187
+ <dbx-button-spacer></dbx-button-spacer>
188
+ <dbx-button text="Deny" [flat]="true" color="warn" (buttonClick)="denyClick.emit()"></dbx-button>
189
+ </div>
190
+ @if (resolvedError()) {
191
+ <dbx-error [error]="resolvedError()"></dbx-error>
192
+ }
193
+ }
194
+ </div>
195
+ `, host: {
196
+ class: 'd-block dbx-firebase-oauth-consent-view'
197
+ }, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".dbx-firebase-oauth-consent-view .dbx-firebase-oauth-consent-header-info{align-items:center;gap:12px}\n"] }]
198
+ }], propDecorators: { details: [{ type: i0.Input, args: [{ isSignal: true, alias: "details", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], scopeInjectionConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "scopeInjectionConfig", required: true }] }], approveClick: [{ type: i0.Output, args: ["approveClick"] }], denyClick: [{ type: i0.Output, args: ["denyClick"] }] } });
199
+
200
+ /**
201
+ * Abstract base class for consent scope view components.
202
+ *
203
+ * Provides typed access to the `DbxFirebaseOAuthConsentScopesViewData` injected
204
+ * via `DBX_INJECTION_COMPONENT_DATA`. Subclasses only need to define a template.
205
+ *
206
+ * @example
207
+ * ```typescript
208
+ * @Component({ template: `...` })
209
+ * export class MyCustomScopesViewComponent extends AbstractDbxFirebaseOAuthConsentScopeViewComponent {}
210
+ * ```
211
+ */
212
+ class AbstractDbxFirebaseOAuthConsentScopeViewComponent {
213
+ data = inject(DBX_INJECTION_COMPONENT_DATA);
214
+ details = computed(() => this.data?.details, ...(ngDevMode ? [{ debugName: "details" }] : []));
215
+ scopes = computed(() => this.data?.scopes ?? [], ...(ngDevMode ? [{ debugName: "scopes" }] : []));
216
+ clientName = computed(() => this.data?.clientName ?? '', ...(ngDevMode ? [{ debugName: "clientName" }] : []));
217
+ clientUri = computed(() => this.data?.details?.client_uri, ...(ngDevMode ? [{ debugName: "clientUri" }] : []));
218
+ logoUri = computed(() => this.data?.details?.logo_uri, ...(ngDevMode ? [{ debugName: "logoUri" }] : []));
219
+ }
220
+
221
+ /**
222
+ * Standalone presentational component that renders a list of OAuth consent scopes.
223
+ *
224
+ * @example
225
+ * ```html
226
+ * <dbx-firebase-oauth-consent-scope-list [scopes]="mappedScopes"></dbx-firebase-oauth-consent-scope-list>
227
+ * ```
228
+ */
229
+ class DbxFirebaseOAuthConsentScopeListComponent {
230
+ scopes = input([], ...(ngDevMode ? [{ debugName: "scopes" }] : []));
231
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOAuthConsentScopeListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
232
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: DbxFirebaseOAuthConsentScopeListComponent, isStandalone: true, selector: "dbx-firebase-oauth-consent-scope-list", inputs: { scopes: { classPropertyName: "scopes", publicName: "scopes", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
233
+ @for (scope of scopes(); track scope.name) {
234
+ <div class="dbx-firebase-oauth-consent-scope-list-item dbx-mb2">
235
+ <span class="dbx-firebase-oauth-consent-scope-name dbx-pb2">{{ scope.name }}</span>
236
+ @if (scope.description) {
237
+ <span class="dbx-firebase-oauth-consent-scope-description">{{ scope.description }}</span>
238
+ }
239
+ </div>
240
+ }
241
+ `, isInline: true, styles: [".dbx-firebase-oauth-consent-scope-list-item{display:flex;flex-direction:column;padding:8px 12px;border-left:3px solid var(--dbx-primary-color);background:color-mix(in srgb,var(--dbx-color-current) 10%,transparent)}.dbx-firebase-oauth-consent-scope-list-item .dbx-firebase-oauth-consent-scope-name{font-weight:500}.dbx-firebase-oauth-consent-scope-list-item .dbx-firebase-oauth-consent-scope-description{font-size:.85em;opacity:.7}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
242
+ }
243
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOAuthConsentScopeListComponent, decorators: [{
244
+ type: Component,
245
+ args: [{ selector: 'dbx-firebase-oauth-consent-scope-list', standalone: true, template: `
246
+ @for (scope of scopes(); track scope.name) {
247
+ <div class="dbx-firebase-oauth-consent-scope-list-item dbx-mb2">
248
+ <span class="dbx-firebase-oauth-consent-scope-name dbx-pb2">{{ scope.name }}</span>
249
+ @if (scope.description) {
250
+ <span class="dbx-firebase-oauth-consent-scope-description">{{ scope.description }}</span>
251
+ }
252
+ </div>
253
+ }
254
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [".dbx-firebase-oauth-consent-scope-list-item{display:flex;flex-direction:column;padding:8px 12px;border-left:3px solid var(--dbx-primary-color);background:color-mix(in srgb,var(--dbx-color-current) 10%,transparent)}.dbx-firebase-oauth-consent-scope-list-item .dbx-firebase-oauth-consent-scope-name{font-weight:500}.dbx-firebase-oauth-consent-scope-list-item .dbx-firebase-oauth-consent-scope-description{font-size:.85em;opacity:.7}\n"] }]
255
+ }], propDecorators: { scopes: [{ type: i0.Input, args: [{ isSignal: true, alias: "scopes", required: false }] }] } });
256
+
257
+ const DEFAULT_OIDC_AUTHORIZATION_ENDPOINT_PATH = '/oidc/auth';
258
+ const DEFAULT_OIDC_INTERACTION_ENDPOINT_PATH = '/interaction';
259
+ const DEFAULT_OIDC_INTERACTION_UID_PARAM_KEY = 'uid';
260
+ const DEFAULT_OIDC_CLIENT_ID_PARAM_KEY = 'client_id';
261
+ const DEFAULT_OIDC_CLIENT_NAME_PARAM_KEY = 'client_name';
262
+ const DEFAULT_OIDC_CLIENT_URI_PARAM_KEY = 'client_uri';
263
+ const DEFAULT_OIDC_LOGO_URI_PARAM_KEY = 'logo_uri';
264
+ const DEFAULT_OIDC_SCOPES_PARAM_KEY = 'scopes';
265
+ const DEFAULT_OIDC_TOKEN_ENDPOINT_AUTH_METHODS = ['client_secret_post', 'client_secret_basic'];
266
+ /**
267
+ * Abstract configuration class used as a DI token for app-level OIDC settings.
268
+ *
269
+ * Apps provide a concrete implementation via `provideDbxFirebaseOidc()`.
270
+ */
271
+ class DbxFirebaseOidcConfig {
272
+ /** Path to the authorization endpoint. Defaults to '/oidc/auth'. */
273
+ oidcAuthorizationEndpointApiPath;
274
+ /** Base path for interaction endpoints. Defaults to '/interaction'. */
275
+ oidcInteractionEndpointApiPath;
276
+ /**
277
+ * Supported token endpoint authentication methods.
278
+ *
279
+ * Overrides the default methods (`client_secret_post`, `client_secret_basic`).
280
+ * Used by forms and UI components that need to know which auth methods are available.
281
+ */
282
+ tokenEndpointAuthMethods;
283
+ /**
284
+ * Frontend route ref for the OAuth interaction pages (login/consent).
285
+ *
286
+ * When provided, this route is registered with {@link DbxAppAuthRouterService} as an
287
+ * ignored route, preventing auth effects from redirecting away during the OIDC flow.
288
+ *
289
+ * Uses hierarchical matching — a parent route ref (e.g., `'app.oauth'`) will cover
290
+ * all child routes (e.g., `'app.oauth.login'`, `'app.oauth.consent'`).
291
+ */
292
+ oauthInteractionRoute;
293
+ /**
294
+ * Component class for rendering the consent scope list.
295
+ *
296
+ * When not provided, uses `DbxFirebaseOAuthConsentScopeDefaultViewComponent` which
297
+ * maps scope names to descriptions from `availableScopes`.
298
+ */
299
+ consentScopeListViewClass;
300
+ }
301
+ /**
302
+ * Service that exposes the app-level OIDC configuration.
303
+ *
304
+ * Inject this service in components to access centralized OIDC settings
305
+ * (scopes, endpoint paths, param keys, etc.) without requiring explicit inputs.
306
+ */
307
+ class DbxFirebaseOidcConfigService {
308
+ config = inject(DbxFirebaseOidcConfig);
309
+ get availableScopes() {
310
+ return this.config.availableScopes;
311
+ }
312
+ get oidcAuthorizationEndpointApiPath() {
313
+ return this.config.oidcAuthorizationEndpointApiPath ?? DEFAULT_OIDC_AUTHORIZATION_ENDPOINT_PATH;
314
+ }
315
+ get oidcInteractionEndpointApiPath() {
316
+ return this.config.oidcInteractionEndpointApiPath ?? DEFAULT_OIDC_INTERACTION_ENDPOINT_PATH;
317
+ }
318
+ get tokenEndpointAuthMethods() {
319
+ return this.config.tokenEndpointAuthMethods ?? DEFAULT_OIDC_TOKEN_ENDPOINT_AUTH_METHODS;
320
+ }
321
+ get oauthInteractionRoute() {
322
+ return this.config.oauthInteractionRoute;
323
+ }
324
+ get consentScopeListViewClass() {
325
+ return this.config.consentScopeListViewClass;
326
+ }
327
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
328
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcConfigService });
329
+ }
330
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcConfigService, decorators: [{
331
+ type: Injectable
332
+ }] });
333
+
334
+ /**
335
+ * Default consent scope view component that maps scope names to descriptions
336
+ * using the `OidcScopeDetails` from the app-level OIDC configuration.
337
+ *
338
+ * Apps can override this by providing a custom `consentScopeListViewClass`
339
+ * in `DbxFirebaseOidcConfig` or `DbxOAuthConsentComponentConfig`.
340
+ */
341
+ class DbxFirebaseOAuthConsentScopeDefaultViewComponent extends AbstractDbxFirebaseOAuthConsentScopeViewComponent {
342
+ oidcConfigService = inject(DbxFirebaseOidcConfigService);
343
+ mappedScopes = computed(() => {
344
+ const availableScopes = this.oidcConfigService.availableScopes;
345
+ const availableScopeValues = new Set(availableScopes.map((s) => s.value));
346
+ const { included: knownScopes, excluded: unknownScopes } = separateValues(this.scopes(), (name) => availableScopeValues.has(name));
347
+ return [
348
+ ...knownScopes.map((name) => {
349
+ const details = availableScopes.find((s) => s.value === name);
350
+ return { name, description: details.description ?? '' };
351
+ }),
352
+ ...unknownScopes.map((name) => ({ name, description: 'unknown' }))
353
+ ];
354
+ }, ...(ngDevMode ? [{ debugName: "mappedScopes" }] : []));
355
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOAuthConsentScopeDefaultViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
356
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.0", type: DbxFirebaseOAuthConsentScopeDefaultViewComponent, isStandalone: true, selector: "dbx-firebase-oauth-consent-scope-default-view", usesInheritance: true, ngImport: i0, template: `
357
+ <p>
358
+ <strong>{{ clientName() }}</strong>
359
+ is requesting these permissions:
360
+ </p>
361
+ <dbx-firebase-oauth-consent-scope-list [scopes]="mappedScopes()"></dbx-firebase-oauth-consent-scope-list>
362
+ `, isInline: true, dependencies: [{ kind: "component", type: DbxFirebaseOAuthConsentScopeListComponent, selector: "dbx-firebase-oauth-consent-scope-list", inputs: ["scopes"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
363
+ }
364
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOAuthConsentScopeDefaultViewComponent, decorators: [{
365
+ type: Component,
366
+ args: [{
367
+ selector: 'dbx-firebase-oauth-consent-scope-default-view',
368
+ standalone: true,
369
+ imports: [DbxFirebaseOAuthConsentScopeListComponent],
370
+ template: `
371
+ <p>
372
+ <strong>{{ clientName() }}</strong>
373
+ is requesting these permissions:
374
+ </p>
375
+ <dbx-firebase-oauth-consent-scope-list [scopes]="mappedScopes()"></dbx-firebase-oauth-consent-scope-list>
376
+ `,
377
+ changeDetection: ChangeDetectionStrategy.OnPush
378
+ }]
379
+ }] });
380
+
381
+ // MARK: Service
382
+ /**
383
+ * Service for communicating with the backend OIDC interaction endpoints.
384
+ *
385
+ * Automatically includes the current user's Firebase Auth ID token
386
+ * with each request for server-side verification.
387
+ *
388
+ * After successful login/consent submission, the server returns a redirect URL.
389
+ * The component is responsible for navigating to it (e.g., via `window.location.href`).
390
+ */
391
+ class DbxFirebaseOidcInteractionService {
392
+ http = inject(HttpClient);
393
+ _authService = inject(DbxFirebaseAuthService);
394
+ _oidcConfig = inject(DbxFirebaseOidcConfigService);
395
+ /**
396
+ * Base URL for the interaction API, derived from the OIDC config service.
397
+ */
398
+ get baseUrl() {
399
+ return this._oidcConfig.oidcInteractionEndpointApiPath;
400
+ }
401
+ /**
402
+ * Submit login to complete the login interaction.
403
+ *
404
+ * Automatically attaches the current user's Firebase ID token.
405
+ *
406
+ * @returns Observable that emits the redirect URL from the server response.
407
+ */
408
+ submitLogin(uid) {
409
+ return this._authService.idTokenString$.pipe(first(), switchMap((idToken) => this.http.post(`${this.baseUrl}/${uid}/login`, { idToken })));
410
+ }
411
+ /**
412
+ * Submit consent decision to complete the consent interaction.
413
+ *
414
+ * Automatically attaches the current user's Firebase ID token.
415
+ *
416
+ * @returns Observable that emits the redirect URL from the server response.
417
+ */
418
+ submitConsent(uid, approved) {
419
+ return this._authService.idTokenString$.pipe(first(), switchMap((idToken) => this.http.post(`${this.baseUrl}/${uid}/consent`, { idToken, approved })));
420
+ }
421
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcInteractionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
422
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcInteractionService, providedIn: 'root' });
423
+ }
424
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcInteractionService, decorators: [{
425
+ type: Injectable,
426
+ args: [{ providedIn: 'root' }]
427
+ }] });
428
+
429
+ /**
430
+ * Container component for the OIDC OAuth login interaction flow.
431
+ *
432
+ * Manages all state: route param reading, Firebase Auth observation, ID token
433
+ * submission, and error handling. Delegates visual rendering to
434
+ * `DbxFirebaseOAuthLoginViewComponent`.
435
+ *
436
+ * Supports ng-content projection — any content provided is passed through to
437
+ * the view component, replacing the default `<dbx-firebase-login>` for the
438
+ * `'no_user'` state.
439
+ *
440
+ * Usage: Route to this component with `?uid=<interaction-uid>` query param.
441
+ */
442
+ class DbxFirebaseOAuthLoginComponent {
443
+ dbxRouterService = inject(DbxRouterService);
444
+ dbxFirebaseAuthService = inject(DbxFirebaseAuthService);
445
+ interactionService = inject(DbxFirebaseOidcInteractionService);
446
+ uidParamReader = dbxRouteParamReaderInstance(this.dbxRouterService, DEFAULT_OIDC_INTERACTION_UID_PARAM_KEY);
447
+ interactionUid = toSignal(this.uidParamReader.value$);
448
+ isLoggedIn = toSignal(this.dbxFirebaseAuthService.isLoggedIn$, { initialValue: false });
449
+ submitting = signal(false, ...(ngDevMode ? [{ debugName: "submitting" }] : []));
450
+ errorMessage = signal(null, ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
451
+ loginStateCase = computed(() => {
452
+ if (this.submitting()) {
453
+ return 'submitting';
454
+ }
455
+ if (this.errorMessage()) {
456
+ return 'error';
457
+ }
458
+ if (!this.isLoggedIn()) {
459
+ return 'no_user';
460
+ }
461
+ return 'user';
462
+ }, ...(ngDevMode ? [{ debugName: "loginStateCase" }] : []));
463
+ constructor() {
464
+ // Auto-submit when user is logged in
465
+ effect(() => {
466
+ if (this.loginStateCase() === 'user') {
467
+ this._submitIdToken();
468
+ }
469
+ });
470
+ }
471
+ ngOnDestroy() {
472
+ this.uidParamReader.destroy();
473
+ }
474
+ retry() {
475
+ this.errorMessage.set(null);
476
+ this._submitIdToken();
477
+ }
478
+ _submitIdToken() {
479
+ const uid = this.interactionUid();
480
+ if (!uid) {
481
+ this.errorMessage.set('Missing interaction UID from route parameters.');
482
+ return;
483
+ }
484
+ this.submitting.set(true);
485
+ this.errorMessage.set(null);
486
+ this.interactionService.submitLogin(uid).subscribe({
487
+ next: (response) => {
488
+ this.submitting.set(false);
489
+ if (response.redirectTo) {
490
+ window.location.href = response.redirectTo;
491
+ }
492
+ },
493
+ error: () => {
494
+ this.submitting.set(false);
495
+ this.errorMessage.set('Failed to complete login. Please try again.');
496
+ }
497
+ });
498
+ }
499
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOAuthLoginComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
500
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.0", type: DbxFirebaseOAuthLoginComponent, isStandalone: true, selector: "dbx-firebase-oauth-login", host: { classAttribute: "d-block dbx-firebase-oauth-login" }, ngImport: i0, template: `
501
+ <dbx-firebase-oauth-login-view [loginStateCase]="loginStateCase()" [error]="errorMessage()" (retryClick)="retry()">
502
+ <ng-content />
503
+ </dbx-firebase-oauth-login-view>
504
+ `, isInline: true, dependencies: [{ kind: "component", type: DbxFirebaseOAuthLoginViewComponent, selector: "dbx-firebase-oauth-login-view", inputs: ["loginStateCase", "error"], outputs: ["retryClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
505
+ }
506
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOAuthLoginComponent, decorators: [{
507
+ type: Component,
508
+ args: [{
509
+ selector: 'dbx-firebase-oauth-login',
510
+ standalone: true,
511
+ imports: [DbxFirebaseOAuthLoginViewComponent],
512
+ template: `
513
+ <dbx-firebase-oauth-login-view [loginStateCase]="loginStateCase()" [error]="errorMessage()" (retryClick)="retry()">
514
+ <ng-content />
515
+ </dbx-firebase-oauth-login-view>
516
+ `,
517
+ host: {
518
+ class: 'd-block dbx-firebase-oauth-login'
519
+ },
520
+ changeDetection: ChangeDetectionStrategy.OnPush
521
+ }]
522
+ }], ctorParameters: () => [] });
523
+
524
+ /**
525
+ * Container component for the OIDC OAuth consent screen.
526
+ *
527
+ * Manages all state: route param reading, consent submission, and error handling.
528
+ * Delegates visual rendering to `DbxFirebaseOAuthConsentViewComponent`.
529
+ *
530
+ * Reads interaction UID and client details from route params (populated by
531
+ * the server redirect), then assembles them into `OAuthInteractionLoginDetails`.
532
+ */
533
+ class DbxOAuthConsentComponent {
534
+ dbxRouterService = inject(DbxRouterService);
535
+ interactionService = inject(DbxFirebaseOidcInteractionService);
536
+ oidcConfigService = inject(DbxFirebaseOidcConfigService);
537
+ // Config input
538
+ config = input(...(ngDevMode ? [undefined, { debugName: "config" }] : []));
539
+ // Route param readers
540
+ interactionUidParamReader = dbxRouteParamReaderInstance(this.dbxRouterService, DEFAULT_OIDC_INTERACTION_UID_PARAM_KEY);
541
+ clientIdParamReader = dbxRouteParamReaderInstance(this.dbxRouterService, DEFAULT_OIDC_CLIENT_ID_PARAM_KEY);
542
+ clientNameParamReader = dbxRouteParamReaderInstance(this.dbxRouterService, DEFAULT_OIDC_CLIENT_NAME_PARAM_KEY);
543
+ clientUriParamReader = dbxRouteParamReaderInstance(this.dbxRouterService, DEFAULT_OIDC_CLIENT_URI_PARAM_KEY);
544
+ logoUriParamReader = dbxRouteParamReaderInstance(this.dbxRouterService, DEFAULT_OIDC_LOGO_URI_PARAM_KEY);
545
+ scopesParamReader = dbxRouteParamReaderInstance(this.dbxRouterService, DEFAULT_OIDC_SCOPES_PARAM_KEY);
546
+ // Signals from route params
547
+ routeUid = toSignal(this.interactionUidParamReader.value$);
548
+ routeClientId = toSignal(this.clientIdParamReader.value$);
549
+ routeClientName = toSignal(this.clientNameParamReader.value$);
550
+ routeClientUri = toSignal(this.clientUriParamReader.value$);
551
+ routeLogoUri = toSignal(this.logoUriParamReader.value$);
552
+ routeScopes = toSignal(this.scopesParamReader.value$);
553
+ // Resolved values
554
+ resolvedInteractionUid = computed(() => this.routeUid(), ...(ngDevMode ? [{ debugName: "resolvedInteractionUid" }] : []));
555
+ resolvedDetails = computed(() => {
556
+ const client_id = this.routeClientId() ?? '';
557
+ const client_name = this.routeClientName();
558
+ const client_uri = this.routeClientUri();
559
+ const logo_uri = this.routeLogoUri();
560
+ const scopes = this.routeScopes() ?? '';
561
+ return {
562
+ client_id,
563
+ client_name,
564
+ client_uri,
565
+ logo_uri,
566
+ scopes
567
+ };
568
+ }, ...(ngDevMode ? [{ debugName: "resolvedDetails" }] : []));
569
+ // Scope injection config: built from the configured scope list view class, falling back to config service, then the default
570
+ scopeInjectionConfig = computed(() => ({
571
+ componentClass: this.config()?.consentScopeListViewClass ?? this.oidcConfigService.consentScopeListViewClass ?? DbxFirebaseOAuthConsentScopeDefaultViewComponent
572
+ }), ...(ngDevMode ? [{ debugName: "scopeInjectionConfig" }] : []));
573
+ loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
574
+ error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : []));
575
+ ngOnDestroy() {
576
+ this.interactionUidParamReader.destroy();
577
+ this.clientIdParamReader.destroy();
578
+ this.clientNameParamReader.destroy();
579
+ this.clientUriParamReader.destroy();
580
+ this.logoUriParamReader.destroy();
581
+ this.scopesParamReader.destroy();
582
+ }
583
+ approve() {
584
+ this._submitConsent(true);
585
+ }
586
+ deny() {
587
+ this._submitConsent(false);
588
+ }
589
+ _submitConsent(approved) {
590
+ const uid = this.resolvedInteractionUid();
591
+ if (!uid) {
592
+ this.error.set('Missing interaction UID');
593
+ return;
594
+ }
595
+ this.loading.set(true);
596
+ this.error.set(null);
597
+ this.interactionService.submitConsent(uid, approved).subscribe({
598
+ next: (response) => {
599
+ this.loading.set(false);
600
+ if (response.redirectTo) {
601
+ window.location.href = response.redirectTo;
602
+ }
603
+ },
604
+ error: () => {
605
+ this.loading.set(false);
606
+ this.error.set('Failed to process consent. Please try again.');
607
+ }
608
+ });
609
+ }
610
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxOAuthConsentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
611
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.0", type: DbxOAuthConsentComponent, isStandalone: true, selector: "dbx-firebase-oauth-consent", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "d-block dbx-firebase-oauth-consent" }, ngImport: i0, template: `
612
+ <dbx-firebase-oauth-consent-view [details]="resolvedDetails()" [loading]="loading()" [error]="error()" [scopeInjectionConfig]="scopeInjectionConfig()" (approveClick)="approve()" (denyClick)="deny()"></dbx-firebase-oauth-consent-view>
613
+ `, isInline: true, dependencies: [{ kind: "component", type: DbxFirebaseOAuthConsentViewComponent, selector: "dbx-firebase-oauth-consent-view", inputs: ["details", "loading", "error", "scopeInjectionConfig"], outputs: ["approveClick", "denyClick"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
614
+ }
615
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxOAuthConsentComponent, decorators: [{
616
+ type: Component,
617
+ args: [{
618
+ selector: 'dbx-firebase-oauth-consent',
619
+ standalone: true,
620
+ imports: [DbxFirebaseOAuthConsentViewComponent],
621
+ template: `
622
+ <dbx-firebase-oauth-consent-view [details]="resolvedDetails()" [loading]="loading()" [error]="error()" [scopeInjectionConfig]="scopeInjectionConfig()" (approveClick)="approve()" (denyClick)="deny()"></dbx-firebase-oauth-consent-view>
623
+ `,
624
+ host: {
625
+ class: 'd-block dbx-firebase-oauth-consent'
626
+ },
627
+ changeDetection: ChangeDetectionStrategy.OnPush
628
+ }]
629
+ }], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }] } });
630
+
631
+ /**
632
+ * Creates fields for the OAuth client create form.
633
+ *
634
+ * Includes `token_endpoint_auth_method` which is immutable after creation.
635
+ */
636
+ function oidcEntryClientFormFields(config) {
637
+ const fields = [];
638
+ if (config?.mode === 'create') {
639
+ fields.push(oidcClientTokenEndpointAuthMethodField(config));
640
+ }
641
+ fields.push(...oidcEntryClientUpdateFormFields());
642
+ return fields;
643
+ }
644
+ function oidcClientTokenEndpointAuthMethodField(config) {
645
+ const allowedAuthMethods = config?.tokenEndpointAuthMethods;
646
+ const options = allowedAuthMethods?.length ? ALL_OIDC_TOKEN_ENDPOINT_AUTH_METHOD_OPTIONS.filter((o) => allowedAuthMethods.includes(o.value)) : ALL_OIDC_TOKEN_ENDPOINT_AUTH_METHOD_OPTIONS;
647
+ return valueSelectionField({
648
+ key: 'token_endpoint_auth_method',
649
+ label: 'Token Endpoint Auth Method',
650
+ description: 'How the client authenticates when exchanging tokens. Cannot be changed after creation.',
651
+ required: true,
652
+ options
653
+ });
654
+ }
655
+ /**
656
+ * Creates fields for updating an existing OAuth client.
657
+ *
658
+ * Excludes `token_endpoint_auth_method` (immutable after creation).
659
+ */
660
+ function oidcEntryClientUpdateFormFields() {
661
+ return [oidcClientNameField(), oidcClientRedirectUrisField(), oidcClientJwksUriField(), oidcClientLogoUriField(), oidcClientHomepageUriField()];
662
+ }
663
+ function oidcClientNameField() {
664
+ return textField({
665
+ key: 'client_name',
666
+ label: 'Client Name',
667
+ description: 'A human-readable name for this OAuth client.',
668
+ required: true,
669
+ maxLength: 200
670
+ });
671
+ }
672
+ function oidcClientRedirectUrisField() {
673
+ return searchableStringChipField({
674
+ key: 'redirect_uris',
675
+ label: 'Redirect URIs',
676
+ description: 'Type a redirect URI (e.g. https://example.com/callback) and press enter to add it.',
677
+ required: true,
678
+ searchOnEmptyText: false,
679
+ textInputValidator: isWebsiteUrlValidator({ requirePrefix: true, allowPorts: true }),
680
+ search: () => of([]),
681
+ displayForValue: (values) => of(values.map((v) => ({ ...v, label: v.value })))
682
+ });
683
+ }
684
+ function oidcClientJwksUriField() {
685
+ return textField({
686
+ key: 'jwks_uri',
687
+ label: 'JWKS URI',
688
+ description: "URL where the client's public JSON Web Key Set can be fetched. Required for private_key_jwt authentication.",
689
+ required: true,
690
+ expressions: {
691
+ hide: (field) => field.model?.token_endpoint_auth_method !== PRIVATE_KEY_JWT_TOKEN_ENDPOINT_AUTH_METHOD
692
+ }
693
+ });
694
+ }
695
+ function oidcClientLogoUriField() {
696
+ return textField({
697
+ key: 'logo_uri',
698
+ label: 'Logo URI',
699
+ description: 'URL of the client logo image (optional).',
700
+ required: false
701
+ });
702
+ }
703
+ function oidcClientHomepageUriField() {
704
+ return textField({
705
+ key: 'client_uri',
706
+ label: 'Homepage URL',
707
+ description: 'URL of the client homepage (optional).',
708
+ required: false
709
+ });
710
+ }
711
+ /**
712
+ * Assembles the form fields for the OAuth test client form.
713
+ */
714
+ function oidcEntryClientTestFormFields(config) {
715
+ return [oidcClientTestClientIdField(), oidcClientTestRedirectUriField(config.redirectUris), oidcClientTestScopesField(config.availableScopes)];
716
+ }
717
+ function oidcClientTestClientIdField() {
718
+ return textField({
719
+ key: 'client_id',
720
+ label: 'Client ID',
721
+ readonly: true
722
+ });
723
+ }
724
+ function oidcClientTestRedirectUriField(redirectUris) {
725
+ const options = redirectUris.map((uri) => ({ label: uri, value: uri }));
726
+ return valueSelectionField({
727
+ key: 'redirect_uri',
728
+ label: 'Redirect URI',
729
+ description: 'Select the redirect URI to use for the test flow.',
730
+ required: true,
731
+ options
732
+ });
733
+ }
734
+ function oidcClientTestScopesField(availableScopes) {
735
+ return pickableItemChipField({
736
+ key: 'scopes',
737
+ label: 'Scopes',
738
+ description: 'Select the scopes to request.',
739
+ showSelectAllButton: true,
740
+ ...pickableValueFieldValuesConfigForStaticLabeledValues(availableScopes)
741
+ });
742
+ }
743
+
744
+ /**
745
+ * Configurable form component for creating or updating an OAuth client.
746
+ *
747
+ * Pass `{ mode: 'create' }` to show all fields including `token_endpoint_auth_method`.
748
+ * Pass `{ mode: 'update' }` to exclude `token_endpoint_auth_method` (immutable after creation).
749
+ *
750
+ * Token endpoint auth methods are pulled from the injected {@link DbxFirebaseOidcConfigService}.
751
+ */
752
+ class DbxFirebaseOidcEntryClientFormComponent extends AbstractConfigAsyncFormlyFormDirective {
753
+ _oidcConfigService = inject(DbxFirebaseOidcConfigService);
754
+ fields$ = this.config$.pipe(map((config) => oidcEntryClientFormFields({
755
+ ...config,
756
+ tokenEndpointAuthMethods: this._oidcConfigService.tokenEndpointAuthMethods
757
+ })));
758
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientFormComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
759
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.0", type: DbxFirebaseOidcEntryClientFormComponent, isStandalone: true, selector: "dbx-firebase-oidc-client-form", providers: dbxFormlyFormComponentProviders(), usesInheritance: true, ngImport: i0, template: "<dbx-formly></dbx-formly>", isInline: true, dependencies: [{ kind: "ngmodule", type: DbxFormlyFormComponentImportsModule }, { kind: "component", type: i1.DbxFormlyComponent, selector: "dbx-formly", exportAs: ["formly"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
760
+ }
761
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientFormComponent, decorators: [{
762
+ type: Component,
763
+ args: [{
764
+ selector: 'dbx-firebase-oidc-client-form',
765
+ template: DBX_FORMLY_FORM_COMPONENT_TEMPLATE,
766
+ providers: dbxFormlyFormComponentProviders(),
767
+ imports: [DbxFormlyFormComponentImportsModule],
768
+ changeDetection: ChangeDetectionStrategy.OnPush,
769
+ standalone: true
770
+ }]
771
+ }] });
772
+
773
+ /**
774
+ * Form component for configuring an OAuth test authorization request.
775
+ *
776
+ * Displays read-only client_id/secret, a redirect URI selector, and scope picker.
777
+ */
778
+ class DbxFirebaseOidcEntryClientTestFormComponent extends AbstractConfigAsyncFormlyFormDirective {
779
+ fields$ = this.config$.pipe(map((config) => oidcEntryClientTestFormFields(config)));
780
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientTestFormComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
781
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.0", type: DbxFirebaseOidcEntryClientTestFormComponent, isStandalone: true, selector: "dbx-firebase-oidc-client-test-form", providers: dbxFormlyFormComponentProviders(), usesInheritance: true, ngImport: i0, template: "<dbx-formly></dbx-formly>", isInline: true, dependencies: [{ kind: "ngmodule", type: DbxFormlyFormComponentImportsModule }, { kind: "component", type: i1.DbxFormlyComponent, selector: "dbx-formly", exportAs: ["formly"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
782
+ }
783
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientTestFormComponent, decorators: [{
784
+ type: Component,
785
+ args: [{
786
+ selector: 'dbx-firebase-oidc-client-test-form',
787
+ template: DBX_FORMLY_FORM_COMPONENT_TEMPLATE,
788
+ providers: dbxFormlyFormComponentProviders(),
789
+ imports: [DbxFormlyFormComponentImportsModule],
790
+ changeDetection: ChangeDetectionStrategy.OnPush,
791
+ standalone: true
792
+ }]
793
+ }] });
794
+
795
+ class DbxFirebaseOidcEntryClientListComponent extends AbstractDbxSelectionListWrapperDirective {
796
+ constructor() {
797
+ super({
798
+ componentClass: DbxFirebaseOidcEntryClientListViewComponent,
799
+ defaultSelectionMode: 'view'
800
+ });
801
+ }
802
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
803
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.0", type: DbxFirebaseOidcEntryClientListComponent, isStandalone: true, selector: "dbx-firebase-oidc-client-list", providers: provideDbxListViewWrapper(DbxFirebaseOidcEntryClientListComponent), usesInheritance: true, ngImport: i0, template: "\n <dbx-list [state]=\"currentState$\" [config]=\"configSignal()\" [hasMore]=\"hasMore()\" [disabled]=\"disabled()\" [selectionMode]=\"selectionModeSignal()\">\n <ng-content top select=\"[top]\"></ng-content>\n <ng-content bottom select=\"[bottom]\"></ng-content>\n <ng-content empty select=\"[empty]\"></ng-content>\n <ng-content emptyLoading select=\"[emptyLoading]\"></ng-content>\n <ng-content end select=\"[end]\"></ng-content>\n </dbx-list>", isInline: true, dependencies: [{ kind: "ngmodule", type: DbxListWrapperComponentImportsModule }, { kind: "component", type: i1$1.DbxListComponent, selector: "dbx-list", inputs: ["padded", "state", "config", "disabled", "selectionMode", "hasMore"], outputs: ["contentScrolled"] }] });
804
+ }
805
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientListComponent, decorators: [{
806
+ type: Component,
807
+ args: [{
808
+ selector: 'dbx-firebase-oidc-client-list',
809
+ template: DEFAULT_LIST_WRAPPER_COMPONENT_CONFIGURATION_TEMPLATE,
810
+ providers: provideDbxListViewWrapper(DbxFirebaseOidcEntryClientListComponent),
811
+ standalone: true,
812
+ imports: [DbxListWrapperComponentImportsModule]
813
+ }]
814
+ }], ctorParameters: () => [] });
815
+ class DbxFirebaseOidcEntryClientListViewComponent extends AbstractDbxSelectionListViewDirective {
816
+ config = {
817
+ componentClass: DbxFirebaseOidcEntryClientListViewItemComponent,
818
+ mapValuesToItemValues: (x) => of(x.map((y) => ({ ...y, itemValue: y })))
819
+ };
820
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientListViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
821
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.0", type: DbxFirebaseOidcEntryClientListViewComponent, isStandalone: true, selector: "dbx-firebase-oidc-client-list-view", providers: provideDbxListView(DbxFirebaseOidcEntryClientListViewComponent), usesInheritance: true, ngImport: i0, template: "<dbx-selection-list-view [config]=\"config\"></dbx-selection-list-view>", isInline: true, dependencies: [{ kind: "ngmodule", type: DbxSelectionValueListViewComponentImportsModule }, { kind: "component", type: i1$1.DbxSelectionValueListViewComponent, selector: "dbx-selection-list-view" }] });
822
+ }
823
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientListViewComponent, decorators: [{
824
+ type: Component,
825
+ args: [{
826
+ selector: 'dbx-firebase-oidc-client-list-view',
827
+ template: DEFAULT_DBX_SELECTION_VALUE_LIST_COMPONENT_CONFIGURATION_TEMPLATE,
828
+ providers: provideDbxListView(DbxFirebaseOidcEntryClientListViewComponent),
829
+ standalone: true,
830
+ imports: [DbxSelectionValueListViewComponentImportsModule]
831
+ }]
832
+ }] });
833
+ // MARK: Item List
834
+ class DbxFirebaseOidcEntryClientListViewItemClientComponent {
835
+ entry = input.required(...(ngDevMode ? [{ debugName: "entry" }] : []));
836
+ get name() {
837
+ const payload = this.entry().payload;
838
+ return payload?.['client_name'] || 'OAuth Client';
839
+ }
840
+ get clientId() {
841
+ const payload = this.entry().payload;
842
+ return payload?.['client_id'] || '';
843
+ }
844
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientListViewItemClientComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
845
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.0", type: DbxFirebaseOidcEntryClientListViewItemClientComponent, isStandalone: true, selector: "dbx-firebase-oidc-client-list-view-item-client", inputs: { entry: { classPropertyName: "entry", publicName: "entry", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
846
+ <div>
847
+ <p>{{ name }}</p>
848
+ <p class="dbx-hint">{{ clientId }}</p>
849
+ </div>
850
+ `, isInline: true });
851
+ }
852
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientListViewItemClientComponent, decorators: [{
853
+ type: Component,
854
+ args: [{
855
+ selector: 'dbx-firebase-oidc-client-list-view-item-client',
856
+ template: `
857
+ <div>
858
+ <p>{{ name }}</p>
859
+ <p class="dbx-hint">{{ clientId }}</p>
860
+ </div>
861
+ `,
862
+ standalone: true
863
+ }]
864
+ }], propDecorators: { entry: [{ type: i0.Input, args: [{ isSignal: true, alias: "entry", required: true }] }] } });
865
+ class DbxFirebaseOidcEntryClientListViewItemDefaultComponent {
866
+ entry = input.required(...(ngDevMode ? [{ debugName: "entry" }] : []));
867
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientListViewItemDefaultComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
868
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.0", type: DbxFirebaseOidcEntryClientListViewItemDefaultComponent, isStandalone: true, selector: "dbx-firebase-oidc-client-list-view-item-default", inputs: { entry: { classPropertyName: "entry", publicName: "entry", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
869
+ <div>
870
+ <p>{{ entry().type }}</p>
871
+ </div>
872
+ `, isInline: true });
873
+ }
874
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientListViewItemDefaultComponent, decorators: [{
875
+ type: Component,
876
+ args: [{
877
+ selector: 'dbx-firebase-oidc-client-list-view-item-default',
878
+ template: `
879
+ <div>
880
+ <p>{{ entry().type }}</p>
881
+ </div>
882
+ `,
883
+ standalone: true
884
+ }]
885
+ }], propDecorators: { entry: [{ type: i0.Input, args: [{ isSignal: true, alias: "entry", required: true }] }] } });
886
+ class DbxFirebaseOidcEntryClientListViewItemComponent extends AbstractDbxValueListViewItemComponent {
887
+ clientType = OIDC_ENTRY_CLIENT_TYPE;
888
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientListViewItemComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
889
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: DbxFirebaseOidcEntryClientListViewItemComponent, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: `
890
+ @switch (itemValue.type) {
891
+ @case (clientType) {
892
+ <dbx-firebase-oidc-client-list-view-item-client [entry]="itemValue"></dbx-firebase-oidc-client-list-view-item-client>
893
+ }
894
+ @default {
895
+ <dbx-firebase-oidc-client-list-view-item-default [entry]="itemValue"></dbx-firebase-oidc-client-list-view-item-default>
896
+ }
897
+ }
898
+ `, isInline: true, dependencies: [{ kind: "component", type: DbxFirebaseOidcEntryClientListViewItemClientComponent, selector: "dbx-firebase-oidc-client-list-view-item-client", inputs: ["entry"] }, { kind: "component", type: DbxFirebaseOidcEntryClientListViewItemDefaultComponent, selector: "dbx-firebase-oidc-client-list-view-item-default", inputs: ["entry"] }] });
899
+ }
900
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientListViewItemComponent, decorators: [{
901
+ type: Component,
902
+ args: [{
903
+ template: `
904
+ @switch (itemValue.type) {
905
+ @case (clientType) {
906
+ <dbx-firebase-oidc-client-list-view-item-client [entry]="itemValue"></dbx-firebase-oidc-client-list-view-item-client>
907
+ }
908
+ @default {
909
+ <dbx-firebase-oidc-client-list-view-item-default [entry]="itemValue"></dbx-firebase-oidc-client-list-view-item-default>
910
+ }
911
+ }
912
+ `,
913
+ standalone: true,
914
+ imports: [DbxFirebaseOidcEntryClientListViewItemClientComponent, DbxFirebaseOidcEntryClientListViewItemDefaultComponent]
915
+ }]
916
+ }] });
917
+
918
+ /** Document store for a single {@link OidcEntry}. */
919
+ class OidcEntryDocumentStore extends AbstractDbxFirebaseDocumentStore {
920
+ oidcModelFunctions = inject(OidcModelFunctions);
921
+ _latestClientSecret$ = completeOnDestroy(new BehaviorSubject(undefined));
922
+ /**
923
+ * The client secret from the most recent create operation.
924
+ *
925
+ * Only available immediately after creation — the server does not return it again.
926
+ */
927
+ latestClientSecret$ = this._latestClientSecret$.asObservable();
928
+ get latestClientSecret() {
929
+ return this._latestClientSecret$.value;
930
+ }
931
+ constructor() {
932
+ super({ firestoreCollection: inject(OidcModelFirestoreCollections).oidcEntryCollection });
933
+ }
934
+ createClient = firebaseDocumentStoreCreateFunction(this, this.oidcModelFunctions.oidcEntry.createOidcEntry.client, {
935
+ onResult: (_params, result) => {
936
+ this._latestClientSecret$.next(result.client_secret);
937
+ }
938
+ });
939
+ updateClient = firebaseDocumentStoreUpdateFunction(this, this.oidcModelFunctions.oidcEntry.updateOidcEntry.client);
940
+ rotateClientSecret = firebaseDocumentStoreUpdateFunction(this, this.oidcModelFunctions.oidcEntry.updateOidcEntry.rotateClientSecret, {
941
+ onResult: (_params, result) => {
942
+ this._latestClientSecret$.next(result.client_secret);
943
+ }
944
+ });
945
+ deleteClient = firebaseDocumentStoreDeleteFunction(this, this.oidcModelFunctions.oidcEntry.deleteOidcEntry.client);
946
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: OidcEntryDocumentStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
947
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: OidcEntryDocumentStore });
948
+ }
949
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: OidcEntryDocumentStore, decorators: [{
950
+ type: Injectable
951
+ }], ctorParameters: () => [] });
952
+
953
+ /**
954
+ * Container component for creating a new OAuth client.
955
+ *
956
+ * Wraps the client form in an action context with a submit button.
957
+ * Emits {@link clientCreated} with the result after successful creation.
958
+ */
959
+ class DbxFirebaseOidcEntryClientCreateComponent {
960
+ oidcEntryDocumentStore = inject(OidcEntryDocumentStore);
961
+ formConfig = { mode: 'create' };
962
+ createClientOwnerTarget = input(...(ngDevMode ? [undefined, { debugName: "createClientOwnerTarget" }] : []));
963
+ clientCreated = output();
964
+ handleCreateClient = (value, context) => {
965
+ const params = value;
966
+ const target = this.createClientOwnerTarget();
967
+ if (target) {
968
+ params.key = target;
969
+ }
970
+ context.startWorkingWithLoadingStateObservable(this.oidcEntryDocumentStore.createClient(params).pipe(tap((state) => {
971
+ if (state.value) {
972
+ this.clientCreated.emit(state.value);
973
+ }
974
+ })));
975
+ };
976
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientCreateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
977
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.0", type: DbxFirebaseOidcEntryClientCreateComponent, isStandalone: true, selector: "dbx-firebase-oidc-entry-client-create", inputs: { createClientOwnerTarget: { classPropertyName: "createClientOwnerTarget", publicName: "createClientOwnerTarget", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clientCreated: "clientCreated" }, ngImport: i0, template: `
978
+ <div dbxAction dbxActionEnforceModified [dbxActionHandler]="handleCreateClient" dbxActionSnackbarError>
979
+ <dbx-firebase-oidc-client-form dbxActionForm [config]="formConfig"></dbx-firebase-oidc-client-form>
980
+ <dbx-button [raised]="true" dbxActionButton text="Create"></dbx-button>
981
+ </div>
982
+ `, isInline: true, dependencies: [{ kind: "directive", type: DbxActionSnackbarErrorDirective, selector: "[dbxActionSnackbarError]", inputs: ["dbxActionSnackbarError"] }, { kind: "directive", type: DbxActionDirective, selector: "dbx-action,[dbxAction]", exportAs: ["action", "dbxAction"] }, { kind: "directive", type: DbxActionEnforceModifiedDirective, selector: "[dbxActionEnforceModified]", inputs: ["dbxActionEnforceModified"] }, { kind: "directive", type: DbxActionHandlerDirective, selector: "[dbxActionHandler]", inputs: ["dbxActionHandler"] }, { kind: "directive", type: DbxActionFormDirective, selector: "[dbxActionForm]", inputs: ["dbxActionFormDisabledOnWorking", "dbxActionFormIsValid", "dbxActionFormIsEqual", "dbxActionFormIsModified", "dbxActionFormMapValue"] }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "mode"] }, { kind: "directive", type: DbxActionButtonDirective, selector: "[dbxActionButton]" }, { kind: "component", type: DbxFirebaseOidcEntryClientFormComponent, selector: "dbx-firebase-oidc-client-form" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
983
+ }
984
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientCreateComponent, decorators: [{
985
+ type: Component,
986
+ args: [{
987
+ selector: 'dbx-firebase-oidc-entry-client-create',
988
+ template: `
989
+ <div dbxAction dbxActionEnforceModified [dbxActionHandler]="handleCreateClient" dbxActionSnackbarError>
990
+ <dbx-firebase-oidc-client-form dbxActionForm [config]="formConfig"></dbx-firebase-oidc-client-form>
991
+ <dbx-button [raised]="true" dbxActionButton text="Create"></dbx-button>
992
+ </div>
993
+ `,
994
+ standalone: true,
995
+ imports: [DbxActionSnackbarErrorDirective, DbxActionDirective, DbxActionEnforceModifiedDirective, DbxActionHandlerDirective, DbxActionFormDirective, DbxButtonComponent, DbxActionButtonDirective, DbxFirebaseOidcEntryClientFormComponent],
996
+ changeDetection: ChangeDetectionStrategy.OnPush
997
+ }]
998
+ }], propDecorators: { createClientOwnerTarget: [{ type: i0.Input, args: [{ isSignal: true, alias: "createClientOwnerTarget", required: false }] }], clientCreated: [{ type: i0.Output, args: ["clientCreated"] }] } });
999
+
1000
+ /**
1001
+ * Generates a random PKCE code verifier string (43 characters, base64url-encoded).
1002
+ *
1003
+ * @returns A cryptographically random base64url string suitable for use as a PKCE code_verifier.
1004
+ */
1005
+ function generatePkceCodeVerifier() {
1006
+ const bytes = new Uint8Array(32);
1007
+ crypto.getRandomValues(bytes);
1008
+ return base64UrlEncode(bytes);
1009
+ }
1010
+ /**
1011
+ * Generates a PKCE code challenge from a code verifier using SHA-256.
1012
+ *
1013
+ * @param verifier - The code verifier string to hash
1014
+ * @returns A base64url-encoded SHA-256 hash of the verifier
1015
+ */
1016
+ async function generatePkceCodeChallenge(verifier) {
1017
+ const encoder = new TextEncoder();
1018
+ const data = encoder.encode(verifier);
1019
+ const digest = await crypto.subtle.digest('SHA-256', data);
1020
+ return base64UrlEncode(new Uint8Array(digest));
1021
+ }
1022
+ function base64UrlEncode(bytes) {
1023
+ const binString = Array.from(bytes, (byte) => String.fromCharCode(byte)).join('');
1024
+ return btoa(binString).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
1025
+ }
1026
+
1027
+ /**
1028
+ * Container component for testing an OAuth authorization flow against a registered client.
1029
+ *
1030
+ * Displays a form with the client's ID, redirect URIs, and scopes,
1031
+ * then builds an authorization URL with PKCE parameters that can be opened in a new tab.
1032
+ */
1033
+ class DbxFirebaseOidcEntryClientTestComponent {
1034
+ oidcEntryDocumentStore = inject(OidcEntryDocumentStore);
1035
+ oidcConfigService = inject(DbxFirebaseOidcConfigService);
1036
+ /** Scopes the user can pick from. Overrides the service default when provided. */
1037
+ availableScopes = input(undefined, ...(ngDevMode ? [{ debugName: "availableScopes" }] : []));
1038
+ /** Path to the authorization endpoint. Overrides the service default when provided. */
1039
+ oidcAuthorizationEndpointApiPath = input(undefined, ...(ngDevMode ? [{ debugName: "oidcAuthorizationEndpointApiPath" }] : []));
1040
+ resolvedAvailableScopes = computed(() => this.availableScopes() ?? this.oidcConfigService.availableScopes, ...(ngDevMode ? [{ debugName: "resolvedAvailableScopes" }] : []));
1041
+ resolvedAuthorizationEndpointPath = computed(() => this.oidcAuthorizationEndpointApiPath() ?? this.oidcConfigService.oidcAuthorizationEndpointApiPath, ...(ngDevMode ? [{ debugName: "resolvedAuthorizationEndpointPath" }] : []));
1042
+ // MARK: Derived Store Data
1043
+ redirectUrisSignal = toSignal(this.oidcEntryDocumentStore.data$.pipe(map((data) => data.payload?.redirect_uris ?? [])));
1044
+ clientIdSignal = toSignal(this.oidcEntryDocumentStore.data$.pipe(map((data) => data.payload?.client_id)));
1045
+ // MARK: Form Config
1046
+ formConfig = computed(() => {
1047
+ const redirectUris = this.redirectUrisSignal();
1048
+ const availableScopes = this.resolvedAvailableScopes();
1049
+ if (redirectUris) {
1050
+ return { redirectUris, availableScopes };
1051
+ }
1052
+ return undefined;
1053
+ }, ...(ngDevMode ? [{ debugName: "formConfig" }] : []));
1054
+ formTemplate$ = this.oidcEntryDocumentStore.data$.pipe(map((data) => {
1055
+ const payload = data.payload;
1056
+ const formValue = {
1057
+ client_id: payload?.client_id ?? '',
1058
+ redirect_uri: payload?.redirect_uris?.[0] ?? '',
1059
+ scopes: ['openid']
1060
+ };
1061
+ return formValue;
1062
+ }));
1063
+ // MARK: PKCE
1064
+ codeVerifier = signal(generatePkceCodeVerifier(), ...(ngDevMode ? [{ debugName: "codeVerifier" }] : []));
1065
+ codeChallenge = signal('', ...(ngDevMode ? [{ debugName: "codeChallenge" }] : []));
1066
+ state = signal(generateRandomString(), ...(ngDevMode ? [{ debugName: "state" }] : []));
1067
+ nonce = signal(generateRandomString(), ...(ngDevMode ? [{ debugName: "nonce" }] : []));
1068
+ /** The current form value, updated by the form via dbxFormValueChange. */
1069
+ formValue = signal(undefined, ...(ngDevMode ? [{ debugName: "formValue" }] : []));
1070
+ authorizationUrlSignal = computed(() => {
1071
+ const clientId = this.clientIdSignal();
1072
+ const codeChallenge = this.codeChallenge();
1073
+ const state = this.state();
1074
+ const nonce = this.nonce();
1075
+ const formValue = this.formValue();
1076
+ if (!clientId || !codeChallenge || !formValue?.redirect_uri) {
1077
+ return undefined;
1078
+ }
1079
+ const params = new URLSearchParams({
1080
+ response_type: 'code',
1081
+ client_id: clientId,
1082
+ redirect_uri: formValue.redirect_uri,
1083
+ scope: (formValue.scopes ?? ['openid']).join(' '),
1084
+ state,
1085
+ nonce,
1086
+ code_challenge: codeChallenge,
1087
+ code_challenge_method: 'S256'
1088
+ });
1089
+ return `${this.resolvedAuthorizationEndpointPath()}?${params.toString()}`;
1090
+ }, ...(ngDevMode ? [{ debugName: "authorizationUrlSignal" }] : []));
1091
+ constructor() {
1092
+ this._updateCodeChallenge();
1093
+ }
1094
+ onFormValueChange(value) {
1095
+ this.formValue.set(value);
1096
+ }
1097
+ openAuthorizationUrl() {
1098
+ const url = this.authorizationUrlSignal();
1099
+ if (url) {
1100
+ window.open(url, '_blank');
1101
+ }
1102
+ }
1103
+ regeneratePkce() {
1104
+ this.codeVerifier.set(generatePkceCodeVerifier());
1105
+ this.state.set(generateRandomString());
1106
+ this.nonce.set(generateRandomString());
1107
+ this._updateCodeChallenge();
1108
+ }
1109
+ _updateCodeChallenge() {
1110
+ generatePkceCodeChallenge(this.codeVerifier()).then((challenge) => {
1111
+ this.codeChallenge.set(challenge);
1112
+ });
1113
+ }
1114
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientTestComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1115
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: DbxFirebaseOidcEntryClientTestComponent, isStandalone: true, selector: "dbx-firebase-oidc-entry-client-test", inputs: { availableScopes: { classPropertyName: "availableScopes", publicName: "availableScopes", isSignal: true, isRequired: false, transformFunction: null }, oidcAuthorizationEndpointApiPath: { classPropertyName: "oidcAuthorizationEndpointApiPath", publicName: "oidcAuthorizationEndpointApiPath", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
1116
+ @if (formConfig()) {
1117
+ <dbx-firebase-oidc-client-test-form [dbxFormSource]="formTemplate$" dbxFormSourceMode="always" [config]="formConfig()" (dbxFormValueChange)="onFormValueChange($event)"></dbx-firebase-oidc-client-test-form>
1118
+ <dbx-content-pit class="dbx-block dbx-mb3" [rounded]="true">
1119
+ <dbx-detail-block class="dbx-pb4" icon="link" header="Authorization URL">
1120
+ @if (authorizationUrlSignal()) {
1121
+ <dbx-click-to-copy-text [copyText]="authorizationUrlSignal()">
1122
+ <div class="dbx-small-text" style="word-break: break-all;">{{ authorizationUrlSignal() }}</div>
1123
+ </dbx-click-to-copy-text>
1124
+ } @else {
1125
+ <div class="dbx-hint">Fill in the form above to generate the URL.</div>
1126
+ }
1127
+ </dbx-detail-block>
1128
+ <dbx-detail-block icon="vpn_key" header="Code Verifier (for token exchange)">
1129
+ <dbx-click-to-copy-text [copyText]="codeVerifier()">{{ codeVerifier() }}</dbx-click-to-copy-text>
1130
+ </dbx-detail-block>
1131
+ </dbx-content-pit>
1132
+ <div class="dbx-mb3">
1133
+ <dbx-button class="dbx-button-spacer" [raised]="true" color="primary" text="Start Authorization Flow" icon="open_in_new" [disabled]="!authorizationUrlSignal()" (buttonClick)="openAuthorizationUrl()"></dbx-button>
1134
+ <dbx-button class="dbx-ml2" text="Regenerate PKCE" icon="refresh" (buttonClick)="regeneratePkce()"></dbx-button>
1135
+ </div>
1136
+ }
1137
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: DbxFirebaseOidcEntryClientTestFormComponent, selector: "dbx-firebase-oidc-client-test-form" }, { kind: "directive", type: DbxFormSourceDirective, selector: "[dbxFormSource]", inputs: ["dbxFormSourceMode", "dbxFormSource"] }, { kind: "directive", type: DbxFormValueChangeDirective, selector: "[dbxFormValueChange]", outputs: ["dbxFormValueChange"] }, { kind: "directive", type: DbxContentPitDirective, selector: "dbx-content-pit, [dbxContentPit]", inputs: ["scrollable", "rounded"] }, { kind: "component", type: DbxDetailBlockComponent, selector: "dbx-detail-block", inputs: ["icon", "header", "alignHeader", "bigHeader"] }, { kind: "component", type: DbxClickToCopyTextComponent, selector: "dbx-click-to-copy-text", inputs: ["copyText", "showIcon", "highlighted", "clipboardSnackbarMessagesConfig", "clipboardSnackbarMessagesEnabled", "clickToCopyIcon", "clickIconToCopyOnly"] }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "mode"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1138
+ }
1139
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientTestComponent, decorators: [{
1140
+ type: Component,
1141
+ args: [{
1142
+ selector: 'dbx-firebase-oidc-entry-client-test',
1143
+ template: `
1144
+ @if (formConfig()) {
1145
+ <dbx-firebase-oidc-client-test-form [dbxFormSource]="formTemplate$" dbxFormSourceMode="always" [config]="formConfig()" (dbxFormValueChange)="onFormValueChange($event)"></dbx-firebase-oidc-client-test-form>
1146
+ <dbx-content-pit class="dbx-block dbx-mb3" [rounded]="true">
1147
+ <dbx-detail-block class="dbx-pb4" icon="link" header="Authorization URL">
1148
+ @if (authorizationUrlSignal()) {
1149
+ <dbx-click-to-copy-text [copyText]="authorizationUrlSignal()">
1150
+ <div class="dbx-small-text" style="word-break: break-all;">{{ authorizationUrlSignal() }}</div>
1151
+ </dbx-click-to-copy-text>
1152
+ } @else {
1153
+ <div class="dbx-hint">Fill in the form above to generate the URL.</div>
1154
+ }
1155
+ </dbx-detail-block>
1156
+ <dbx-detail-block icon="vpn_key" header="Code Verifier (for token exchange)">
1157
+ <dbx-click-to-copy-text [copyText]="codeVerifier()">{{ codeVerifier() }}</dbx-click-to-copy-text>
1158
+ </dbx-detail-block>
1159
+ </dbx-content-pit>
1160
+ <div class="dbx-mb3">
1161
+ <dbx-button class="dbx-button-spacer" [raised]="true" color="primary" text="Start Authorization Flow" icon="open_in_new" [disabled]="!authorizationUrlSignal()" (buttonClick)="openAuthorizationUrl()"></dbx-button>
1162
+ <dbx-button class="dbx-ml2" text="Regenerate PKCE" icon="refresh" (buttonClick)="regeneratePkce()"></dbx-button>
1163
+ </div>
1164
+ }
1165
+ `,
1166
+ standalone: true,
1167
+ imports: [CommonModule, DbxFirebaseOidcEntryClientTestFormComponent, DbxFormSourceDirective, DbxFormValueChangeDirective, DbxContentPitDirective, DbxDetailBlockComponent, DbxClickToCopyTextComponent, DbxButtonComponent],
1168
+ changeDetection: ChangeDetectionStrategy.OnPush
1169
+ }]
1170
+ }], ctorParameters: () => [], propDecorators: { availableScopes: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableScopes", required: false }] }], oidcAuthorizationEndpointApiPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "oidcAuthorizationEndpointApiPath", required: false }] }] } });
1171
+ function generateRandomString() {
1172
+ const bytes = new Uint8Array(16);
1173
+ crypto.getRandomValues(bytes);
1174
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
1175
+ }
1176
+
1177
+ /**
1178
+ * Container component for updating an existing OAuth client.
1179
+ *
1180
+ * Wraps the client update form in an action context with a save button.
1181
+ */
1182
+ class DbxFirebaseOidcEntryClientUpdateComponent {
1183
+ oidcEntryDocumentStore = inject(OidcEntryDocumentStore);
1184
+ formConfig = { mode: 'update' };
1185
+ formTemplate$ = this.oidcEntryDocumentStore.data$.pipe(map((data) => {
1186
+ const payload = data.payload;
1187
+ const formValue = {
1188
+ client_name: payload.client_name ?? '',
1189
+ redirect_uris: payload.redirect_uris ?? [],
1190
+ logo_uri: payload.logo_uri,
1191
+ client_uri: payload.client_uri
1192
+ };
1193
+ return formValue;
1194
+ }));
1195
+ handleUpdateClient = (value, context) => {
1196
+ const params = value;
1197
+ context.startWorkingWithLoadingStateObservable(this.oidcEntryDocumentStore.updateClient(params));
1198
+ };
1199
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientUpdateComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1200
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.0", type: DbxFirebaseOidcEntryClientUpdateComponent, isStandalone: true, selector: "dbx-firebase-oidc-entry-client-update", ngImport: i0, template: `
1201
+ <div dbxAction dbxActionEnforceModified [dbxActionHandler]="handleUpdateClient" dbxActionSnackbarError>
1202
+ <dbx-firebase-oidc-client-form dbxActionForm [dbxFormSource]="formTemplate$" [config]="formConfig"></dbx-firebase-oidc-client-form>
1203
+ <dbx-button [raised]="true" dbxActionButton text="Save"></dbx-button>
1204
+ </div>
1205
+ `, isInline: true, dependencies: [{ kind: "directive", type: DbxActionSnackbarErrorDirective, selector: "[dbxActionSnackbarError]", inputs: ["dbxActionSnackbarError"] }, { kind: "directive", type: DbxActionDirective, selector: "dbx-action,[dbxAction]", exportAs: ["action", "dbxAction"] }, { kind: "directive", type: DbxActionEnforceModifiedDirective, selector: "[dbxActionEnforceModified]", inputs: ["dbxActionEnforceModified"] }, { kind: "directive", type: DbxActionHandlerDirective, selector: "[dbxActionHandler]", inputs: ["dbxActionHandler"] }, { kind: "directive", type: DbxActionFormDirective, selector: "[dbxActionForm]", inputs: ["dbxActionFormDisabledOnWorking", "dbxActionFormIsValid", "dbxActionFormIsEqual", "dbxActionFormIsModified", "dbxActionFormMapValue"] }, { kind: "directive", type: DbxFormSourceDirective, selector: "[dbxFormSource]", inputs: ["dbxFormSourceMode", "dbxFormSource"] }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "mode"] }, { kind: "directive", type: DbxActionButtonDirective, selector: "[dbxActionButton]" }, { kind: "component", type: DbxFirebaseOidcEntryClientFormComponent, selector: "dbx-firebase-oidc-client-form" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1206
+ }
1207
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientUpdateComponent, decorators: [{
1208
+ type: Component,
1209
+ args: [{
1210
+ selector: 'dbx-firebase-oidc-entry-client-update',
1211
+ template: `
1212
+ <div dbxAction dbxActionEnforceModified [dbxActionHandler]="handleUpdateClient" dbxActionSnackbarError>
1213
+ <dbx-firebase-oidc-client-form dbxActionForm [dbxFormSource]="formTemplate$" [config]="formConfig"></dbx-firebase-oidc-client-form>
1214
+ <dbx-button [raised]="true" dbxActionButton text="Save"></dbx-button>
1215
+ </div>
1216
+ `,
1217
+ standalone: true,
1218
+ imports: [DbxActionSnackbarErrorDirective, DbxActionDirective, DbxActionEnforceModifiedDirective, DbxActionHandlerDirective, DbxActionFormDirective, DbxFormSourceDirective, DbxButtonComponent, DbxActionButtonDirective, DbxFirebaseOidcEntryClientFormComponent],
1219
+ changeDetection: ChangeDetectionStrategy.OnPush
1220
+ }]
1221
+ }] });
1222
+
1223
+ /**
1224
+ * Displays the OIDC client ID and (when available) the one-time client secret.
1225
+ *
1226
+ * The client secret is only shown immediately after creation or after rotating.
1227
+ * When no secret is available, a "Rotate Secret" button is shown.
1228
+ */
1229
+ class DbxFirebaseOidcEntryClientViewComponent {
1230
+ oidcEntryDocumentStore = inject(OidcEntryDocumentStore);
1231
+ clientIdSignal = toSignal(this.oidcEntryDocumentStore.data$.pipe(map((data) => data.payload?.client_id)));
1232
+ latestClientSecretSignal = toSignal(this.oidcEntryDocumentStore.latestClientSecret$);
1233
+ rotateSecretConfirmConfig = {
1234
+ title: 'Rotate Client Secret',
1235
+ prompt: 'This will invalidate the current client secret. Any applications using it will stop working. Are you sure?',
1236
+ confirmText: 'Rotate Secret'
1237
+ };
1238
+ handleRotateClientSecret = (_, context) => {
1239
+ context.startWorkingWithLoadingStateObservable(this.oidcEntryDocumentStore.rotateClientSecret({}));
1240
+ };
1241
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1242
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: DbxFirebaseOidcEntryClientViewComponent, isStandalone: true, selector: "dbx-firebase-oidc-entry-client-view", ngImport: i0, template: `
1243
+ <dbx-content-pit [rounded]="true">
1244
+ <dbx-detail-block class="dbx-pb4" icon="key" header="Client ID">
1245
+ <dbx-click-to-copy-text [copyText]="clientIdSignal()">{{ clientIdSignal() }}</dbx-click-to-copy-text>
1246
+ </dbx-detail-block>
1247
+ <dbx-detail-block icon="lock" header="Client Secret">
1248
+ @if (latestClientSecretSignal()) {
1249
+ <dbx-click-to-copy-text class="dbx-block dbx-pb2" [copyText]="latestClientSecretSignal()">{{ latestClientSecretSignal() }}</dbx-click-to-copy-text>
1250
+ <dbx-click-to-copy-text [copyText]="latestClientSecretSignal()" [showIcon]="false"><div class="dbx-hint dbx-u">This secret is only shown once. Copy it now.</div></dbx-click-to-copy-text>
1251
+ } @else {
1252
+ <div>
1253
+ <div class="dbx-hint dbx-pb3">The client secret was shown once when created. You can invalidate the old one and get a new one.</div>
1254
+ <dbx-button dbxAction [dbxActionHandler]="handleRotateClientSecret" [dbxActionConfirm]="rotateSecretConfirmConfig" dbxActionButton text="Rotate Secret" icon="refresh" color="warn" [raised]="true"></dbx-button>
1255
+ </div>
1256
+ }
1257
+ </dbx-detail-block>
1258
+ </dbx-content-pit>
1259
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: DbxContentPitDirective, selector: "dbx-content-pit, [dbxContentPit]", inputs: ["scrollable", "rounded"] }, { kind: "component", type: DbxDetailBlockComponent, selector: "dbx-detail-block", inputs: ["icon", "header", "alignHeader", "bigHeader"] }, { kind: "component", type: DbxClickToCopyTextComponent, selector: "dbx-click-to-copy-text", inputs: ["copyText", "showIcon", "highlighted", "clipboardSnackbarMessagesConfig", "clipboardSnackbarMessagesEnabled", "clickToCopyIcon", "clickIconToCopyOnly"] }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "mode"] }, { kind: "directive", type: DbxActionDirective, selector: "dbx-action,[dbxAction]", exportAs: ["action", "dbxAction"] }, { kind: "directive", type: DbxActionHandlerDirective, selector: "[dbxActionHandler]", inputs: ["dbxActionHandler"] }, { kind: "directive", type: DbxActionButtonDirective, selector: "[dbxActionButton]" }, { kind: "directive", type: DbxActionConfirmDirective, selector: "[dbxActionConfirm]", inputs: ["dbxActionConfirm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1260
+ }
1261
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: DbxFirebaseOidcEntryClientViewComponent, decorators: [{
1262
+ type: Component,
1263
+ args: [{
1264
+ selector: 'dbx-firebase-oidc-entry-client-view',
1265
+ template: `
1266
+ <dbx-content-pit [rounded]="true">
1267
+ <dbx-detail-block class="dbx-pb4" icon="key" header="Client ID">
1268
+ <dbx-click-to-copy-text [copyText]="clientIdSignal()">{{ clientIdSignal() }}</dbx-click-to-copy-text>
1269
+ </dbx-detail-block>
1270
+ <dbx-detail-block icon="lock" header="Client Secret">
1271
+ @if (latestClientSecretSignal()) {
1272
+ <dbx-click-to-copy-text class="dbx-block dbx-pb2" [copyText]="latestClientSecretSignal()">{{ latestClientSecretSignal() }}</dbx-click-to-copy-text>
1273
+ <dbx-click-to-copy-text [copyText]="latestClientSecretSignal()" [showIcon]="false"><div class="dbx-hint dbx-u">This secret is only shown once. Copy it now.</div></dbx-click-to-copy-text>
1274
+ } @else {
1275
+ <div>
1276
+ <div class="dbx-hint dbx-pb3">The client secret was shown once when created. You can invalidate the old one and get a new one.</div>
1277
+ <dbx-button dbxAction [dbxActionHandler]="handleRotateClientSecret" [dbxActionConfirm]="rotateSecretConfirmConfig" dbxActionButton text="Rotate Secret" icon="refresh" color="warn" [raised]="true"></dbx-button>
1278
+ </div>
1279
+ }
1280
+ </dbx-detail-block>
1281
+ </dbx-content-pit>
1282
+ `,
1283
+ standalone: true,
1284
+ imports: [CommonModule, DbxContentPitDirective, DbxDetailBlockComponent, DbxClickToCopyTextComponent, DbxButtonComponent, DbxActionDirective, DbxActionHandlerDirective, DbxActionButtonDirective, DbxActionConfirmDirective],
1285
+ changeDetection: ChangeDetectionStrategy.OnPush
1286
+ }]
1287
+ }] });
1288
+
1289
+ /** Collection store for querying {@link OidcEntry} documents. */
1290
+ class OidcEntryCollectionStore extends AbstractDbxFirebaseCollectionStore {
1291
+ constructor() {
1292
+ super({ firestoreCollection: inject(OidcModelFirestoreCollections).oidcEntryCollection });
1293
+ }
1294
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: OidcEntryCollectionStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1295
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: OidcEntryCollectionStore });
1296
+ }
1297
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: OidcEntryCollectionStore, decorators: [{
1298
+ type: Injectable
1299
+ }], ctorParameters: () => [] });
1300
+
1301
+ /** Directive providing a {@link OidcEntryCollectionStore} for querying {@link OidcEntry} documents. */
1302
+ class OidcEntryCollectionStoreDirective extends DbxFirebaseCollectionStoreDirective {
1303
+ constructor() {
1304
+ super(inject(OidcEntryCollectionStore));
1305
+ }
1306
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: OidcEntryCollectionStoreDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1307
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.0", type: OidcEntryCollectionStoreDirective, isStandalone: true, selector: "[dbxOidcEntryCollection]", providers: provideDbxFirebaseCollectionStoreDirective(OidcEntryCollectionStoreDirective, OidcEntryCollectionStore), usesInheritance: true, ngImport: i0 });
1308
+ }
1309
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: OidcEntryCollectionStoreDirective, decorators: [{
1310
+ type: Directive,
1311
+ args: [{
1312
+ selector: '[dbxOidcEntryCollection]',
1313
+ providers: provideDbxFirebaseCollectionStoreDirective(OidcEntryCollectionStoreDirective, OidcEntryCollectionStore),
1314
+ standalone: true
1315
+ }]
1316
+ }], ctorParameters: () => [] });
1317
+
1318
+ /** Directive providing a {@link OidcEntryDocumentStore} for accessing a single {@link OidcEntry} document. */
1319
+ class OidcEntryDocumentStoreDirective extends DbxFirebaseDocumentStoreDirective {
1320
+ constructor() {
1321
+ super(inject(OidcEntryDocumentStore));
1322
+ }
1323
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: OidcEntryDocumentStoreDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1324
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.0", type: OidcEntryDocumentStoreDirective, isStandalone: true, selector: "[dbxOidcEntryDocument]", providers: provideDbxFirebaseDocumentStoreDirective(OidcEntryDocumentStoreDirective, OidcEntryDocumentStore), usesInheritance: true, ngImport: i0 });
1325
+ }
1326
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: OidcEntryDocumentStoreDirective, decorators: [{
1327
+ type: Directive,
1328
+ args: [{
1329
+ selector: '[dbxOidcEntryDocument]',
1330
+ providers: provideDbxFirebaseDocumentStoreDirective(OidcEntryDocumentStoreDirective, OidcEntryDocumentStore),
1331
+ standalone: true
1332
+ }]
1333
+ }], ctorParameters: () => [] });
1334
+
1335
+ /**
1336
+ * Provider factory for the {@link OidcModelFirestoreCollections}.
1337
+ */
1338
+ function provideOidcModelFirestoreCollections(appCollection) {
1339
+ if (!appCollection.oidcEntryCollection) {
1340
+ throw new Error(`OidcModelFirestoreCollections could not be provided using the app's app collection. Set provideOidcModelFirestoreCollections to false in ProvideDbxFirebaseOidcConfig to prevent auto-initialization, or update your app's collection class to implement OidcModelFirestoreCollections.`);
1341
+ }
1342
+ return appCollection;
1343
+ }
1344
+ /**
1345
+ * Provides the OIDC-related Angular services and collections for `@dereekb/dbx-firebase/oidc`.
1346
+ *
1347
+ * When `oauthInteractionRoute` is configured in {@link DbxFirebaseOidcConfig}, an app initializer
1348
+ * is registered that adds that route to the {@link DbxAppAuthRouterService} ignored routes set,
1349
+ * preventing auth effects from redirecting away during the OIDC interaction flow.
1350
+ */
1351
+ function provideDbxFirebaseOidc(config) {
1352
+ const providers = [{ provide: DbxFirebaseOidcConfig, useValue: config.oidcConfig }, DbxFirebaseOidcConfigService];
1353
+ if (config.provideOidcModelFirestoreCollections !== false) {
1354
+ providers.push({
1355
+ provide: OidcModelFirestoreCollections,
1356
+ useFactory: provideOidcModelFirestoreCollections,
1357
+ deps: [config.appCollectionClass]
1358
+ });
1359
+ }
1360
+ // Register the OAuth interaction route as ignored by auth effects
1361
+ if (config.oidcConfig.oauthInteractionRoute) {
1362
+ const routeRef = config.oidcConfig.oauthInteractionRoute;
1363
+ providers.push(provideAppInitializer(() => {
1364
+ const authRouterService = inject(DbxAppAuthRouterService);
1365
+ authRouterService.addIgnoredRoute(routeRef);
1366
+ }));
1367
+ }
1368
+ return makeEnvironmentProviders(providers);
1369
+ }
1370
+
1371
+ // @dereekb/dbx-firebase/oidc
1372
+
1373
+ /**
1374
+ * Generated bundle index. Do not edit.
1375
+ */
1376
+
1377
+ export { AbstractDbxFirebaseOAuthConsentScopeViewComponent, DEFAULT_OIDC_AUTHORIZATION_ENDPOINT_PATH, DEFAULT_OIDC_CLIENT_ID_PARAM_KEY, DEFAULT_OIDC_CLIENT_NAME_PARAM_KEY, DEFAULT_OIDC_CLIENT_URI_PARAM_KEY, DEFAULT_OIDC_INTERACTION_ENDPOINT_PATH, DEFAULT_OIDC_INTERACTION_UID_PARAM_KEY, DEFAULT_OIDC_LOGO_URI_PARAM_KEY, DEFAULT_OIDC_SCOPES_PARAM_KEY, DEFAULT_OIDC_TOKEN_ENDPOINT_AUTH_METHODS, DbxFirebaseOAuthConsentScopeDefaultViewComponent, DbxFirebaseOAuthConsentScopeListComponent, DbxFirebaseOAuthConsentViewComponent, DbxFirebaseOAuthLoginComponent, DbxFirebaseOAuthLoginViewComponent, DbxFirebaseOidcConfig, DbxFirebaseOidcConfigService, DbxFirebaseOidcEntryClientCreateComponent, DbxFirebaseOidcEntryClientFormComponent, DbxFirebaseOidcEntryClientListComponent, DbxFirebaseOidcEntryClientListViewComponent, DbxFirebaseOidcEntryClientListViewItemClientComponent, DbxFirebaseOidcEntryClientListViewItemComponent, DbxFirebaseOidcEntryClientListViewItemDefaultComponent, DbxFirebaseOidcEntryClientTestComponent, DbxFirebaseOidcEntryClientTestFormComponent, DbxFirebaseOidcEntryClientUpdateComponent, DbxFirebaseOidcEntryClientViewComponent, DbxFirebaseOidcInteractionService, DbxOAuthConsentComponent, OidcEntryCollectionStore, OidcEntryCollectionStoreDirective, OidcEntryDocumentStore, OidcEntryDocumentStoreDirective, generatePkceCodeChallenge, generatePkceCodeVerifier, oidcClientHomepageUriField, oidcClientJwksUriField, oidcClientLogoUriField, oidcClientNameField, oidcClientRedirectUrisField, oidcClientTestClientIdField, oidcClientTestRedirectUriField, oidcClientTestScopesField, oidcClientTokenEndpointAuthMethodField, oidcEntryClientFormFields, oidcEntryClientTestFormFields, oidcEntryClientUpdateFormFields, provideDbxFirebaseOidc, provideOidcModelFirestoreCollections };
1378
+ //# sourceMappingURL=dereekb-dbx-firebase-oidc.mjs.map