@dereekb/dbx-firebase 13.11.3 → 13.11.5

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.
@@ -1,17 +1,18 @@
1
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 * as i1$1 from '@dereekb/dbx-web';
4
- 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';
2
+ import { input, computed, output, ChangeDetectionStrategy, Component, inject, Injectable, signal, effect, Directive, viewChild, provideAppInitializer, makeEnvironmentProviders } from '@angular/core';
3
+ import * as i1 from '@dereekb/dbx-web';
4
+ import { DbxBasicLoadingComponent, DbxErrorComponent, DbxButtonComponent, DbxAvatarComponent, DbxButtonSpacerDirective, DbxActionSnackbarErrorDirective, AbstractDbxSelectionListWrapperDirective, DbxListWrapperComponentImportsModule, DEFAULT_LIST_WRAPPER_COMPONENT_CONFIGURATION_TEMPLATE, AbstractDbxSelectionListViewDirective, DbxSelectionValueListViewComponentImportsModule, provideDbxListView, DEFAULT_DBX_SELECTION_VALUE_LIST_COMPONENT_CONFIGURATION_TEMPLATE, AbstractDbxValueListViewItemComponent, provideDbxListViewWrapper, DbxSpacerDirective, DbxActionConfirmDirective, DbxContentPitDirective, DbxDetailBlockComponent, DbxClickToCopyTextComponent } from '@dereekb/dbx-web';
5
5
  import { readableError, SPACE_STRING_SPLIT_JOIN, separateValues, generatePkceCodeVerifier, generatePkceCodeChallenge } from '@dereekb/util';
6
- import { DbxInjectionComponent, DBX_INJECTION_COMPONENT_DATA, DbxRouterService, dbxRouteParamReaderInstance, completeOnDestroy, DbxActionDirective, DbxActionEnforceModifiedDirective, DbxActionHandlerDirective, DbxActionButtonDirective, DbxAppAuthRouterService } from '@dereekb/dbx-core';
6
+ import { DbxInjectionComponent, DbxActionDirective, DbxActionHandlerDirective, DbxActionValueDirective, DbxActionButtonDirective, DBX_INJECTION_COMPONENT_DATA, DbxRouterService, dbxRouteParamReaderInstance, completeOnDestroy, DbxActionEnforceModifiedDirective, DbxAppAuthRouterService } from '@dereekb/dbx-core';
7
+ import { of, map, first, switchMap, tap, BehaviorSubject } from 'rxjs';
8
+ import * as i1$1 from '@dereekb/dbx-form';
9
+ import { dbxForgeListSelectionField, AbstractConfigAsyncForgeFormDirective, DbxForgeFormComponentImportsModule, dbxForgeFormComponentProviders, DBX_FORGE_FORM_COMPONENT_TEMPLATE, DbxActionFormDirective, dbxForgeValueSelectionField, dbxForgeTextField, dbxForgeSearchableStringChipField, isWebsiteUrlValidator, dbxForgeContainer, dbxForgePickableChipField, pickableValueFieldValuesConfigForStaticLabeledValues, DbxFormSourceDirective, DbxFormValueChangeDirective } from '@dereekb/dbx-form';
10
+ import { successResult } from '@dereekb/rxjs';
7
11
  import { toSignal } from '@angular/core/rxjs-interop';
8
- import { DbxFirebaseAuthService, AbstractDbxFirebaseDocumentStore, firebaseDocumentStoreCreateFunction, firebaseDocumentStoreUpdateFunction, firebaseDocumentStoreDeleteFunction, AbstractDbxFirebaseCollectionStore, DbxFirebaseCollectionStoreDirective, provideDbxFirebaseCollectionStoreDirective, DbxFirebaseDocumentStoreDirective, provideDbxFirebaseDocumentStoreDirective } from '@dereekb/dbx-firebase';
12
+ import { DbxFirebaseAuthService, AbstractDbxFirebaseDocumentStore, firebaseDocumentStoreCreateFunction, firebaseDocumentStoreUpdateFunction, firebaseDocumentStoreDeleteFunction, AbstractDbxFirebaseCollectionStore, DbxFirebaseCollectionStoreDirective, provideDbxFirebaseCollectionStoreDirective, DbxFirebaseCollectionListDirective, DbxFirebaseCollectionChangeDirective, DbxFirebaseDocumentStoreDirective, provideDbxFirebaseDocumentStoreDirective } from '@dereekb/dbx-firebase';
9
13
  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 { dbxForgeValueSelectionField, dbxForgeTextField, dbxForgeSearchableStringChipField, isWebsiteUrlValidator, dbxForgeContainer, dbxForgePickableChipField, pickableValueFieldValuesConfigForStaticLabeledValues, AbstractConfigAsyncForgeFormDirective, DbxForgeFormComponentImportsModule, dbxForgeFormComponentProviders, DBX_FORGE_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';
14
+ import { ALL_OIDC_TOKEN_ENDPOINT_AUTH_METHOD_OPTIONS, PRIVATE_KEY_JWT_TOKEN_ENDPOINT_AUTH_METHOD, OidcModelFunctions, OidcModelFirestoreCollections, OIDC_ENTRY_CLIENT_TYPE, oidcGrantEntriesByUidQuery } from '@dereekb/firebase';
15
+ import { DatePipe, CommonModule } from '@angular/common';
15
16
 
16
17
  /**
17
18
  * Presentational component for the OIDC OAuth login interaction.
@@ -39,6 +40,9 @@ class DbxFirebaseOAuthLoginViewComponent {
39
40
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", 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
41
  <div class="dbx-firebase-oauth-login-view">
41
42
  @switch (loginStateCase()) {
43
+ @case ('unknown') {
44
+ <dbx-basic-loading [loading]="true"></dbx-basic-loading>
45
+ }
42
46
  @case ('no_user') {
43
47
  <ng-content></ng-content>
44
48
  }
@@ -65,6 +69,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
65
69
  template: `
66
70
  <div class="dbx-firebase-oauth-login-view">
67
71
  @switch (loginStateCase()) {
72
+ @case ('unknown') {
73
+ <dbx-basic-loading [loading]="true"></dbx-basic-loading>
74
+ }
68
75
  @case ('no_user') {
69
76
  <ng-content></ng-content>
70
77
  }
@@ -88,120 +95,174 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
88
95
  }]
89
96
  }], 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
97
 
98
+ /**
99
+ * Default required scopes — `openid` is mandatory for any OIDC flow, so the
100
+ * UI always treats it as not-deselectable.
101
+ */
102
+ const DEFAULT_OAUTH_CONSENT_REQUIRED_SCOPES = ['openid'];
91
103
  /**
92
104
  * Presentational component for the OIDC OAuth consent screen.
93
105
  *
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.
106
+ * Wires up two `dbxAction` contexts an outer one for Approve (which hosts
107
+ * the scope-selection forge form via `dbxActionForm`) and a nested one for
108
+ * Deny (which carries no value). Buttons are bound by Angular DI's
109
+ * nearest-ancestor lookup: the Approve button picks up the outer action, the
110
+ * Deny button (wrapped in its own `<ng-container dbxAction>`) picks up the
111
+ * inner.
112
+ *
113
+ * Supports ng-content projection — anything provided is rendered for the
114
+ * `'no_user'` state (so apps can project a login view, mirroring
115
+ * `DbxFirebaseOAuthLoginViewComponent`).
97
116
  *
98
117
  * @example
99
118
  * ```html
100
119
  * <dbx-firebase-oauth-consent-view
101
120
  * [details]="loginDetails"
102
- * [loading]="false"
121
+ * [consentStateCase]="'user'"
103
122
  * [scopeInjectionConfig]="scopeConfig"
104
- * (approveClick)="onApprove()"
105
- * (denyClick)="onDeny()">
123
+ * [approveHandler]="handleApprove"
124
+ * [denyHandler]="handleDeny">
106
125
  * </dbx-firebase-oauth-consent-view>
107
126
  * ```
108
127
  */
109
128
  class DbxFirebaseOAuthConsentViewComponent {
110
129
  details = input(...(ngDevMode ? [undefined, { debugName: "details" }] : /* istanbul ignore next */ []));
111
- loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
112
- error = input(...(ngDevMode ? [undefined, { debugName: "error" }] : /* istanbul ignore next */ []));
130
+ consentStateCase = input.required(...(ngDevMode ? [{ debugName: "consentStateCase" }] : /* istanbul ignore next */ []));
113
131
  scopeInjectionConfig = input.required(...(ngDevMode ? [{ debugName: "scopeInjectionConfig" }] : /* istanbul ignore next */ []));
132
+ /**
133
+ * Scopes that cannot be deselected by the user. Forwarded to the scope
134
+ * view so it can render an "Always granted" hint. Defaults to `['openid']`.
135
+ */
136
+ requiredScopes = input(DEFAULT_OAUTH_CONSENT_REQUIRED_SCOPES, ...(ngDevMode ? [{ debugName: "requiredScopes" }] : /* istanbul ignore next */ []));
137
+ /**
138
+ * Approve handler — called with the form value when the Approve button
139
+ * triggers the outer action. Receives a `WorkUsingContext` to drive the
140
+ * action's loading/success/error pipeline.
141
+ */
142
+ approveHandler = input.required(...(ngDevMode ? [{ debugName: "approveHandler" }] : /* istanbul ignore next */ []));
143
+ /**
144
+ * Deny handler — called when the Deny button triggers the inner action.
145
+ * No value is passed (`dbxActionValue` provides an empty payload).
146
+ */
147
+ denyHandler = input.required(...(ngDevMode ? [{ debugName: "denyHandler" }] : /* istanbul ignore next */ []));
114
148
  clientName = computed(() => this.details()?.client_name ?? '', ...(ngDevMode ? [{ debugName: "clientName" }] : /* istanbul ignore next */ []));
115
149
  clientUri = computed(() => this.details()?.client_uri, ...(ngDevMode ? [{ debugName: "clientUri" }] : /* istanbul ignore next */ []));
116
150
  logoUri = computed(() => this.details()?.logo_uri, ...(ngDevMode ? [{ debugName: "logoUri" }] : /* istanbul ignore next */ []));
117
151
  scopes = computed(() => SPACE_STRING_SPLIT_JOIN.splitStrings(this.details()?.scopes ?? ''), ...(ngDevMode ? [{ debugName: "scopes" }] : /* istanbul ignore next */ []));
118
- resolvedError = computed(() => {
119
- const error = this.error();
120
- return typeof error === 'string' ? readableError('ERROR', error) : error;
121
- }, ...(ngDevMode ? [{ debugName: "resolvedError" }] : /* istanbul ignore next */ []));
122
- approveClick = output();
123
- denyClick = output();
124
152
  resolvedScopeInjectionConfig = computed(() => {
125
153
  const data = {
126
154
  details: this.details(),
127
155
  scopes: this.scopes(),
128
- clientName: this.clientName()
156
+ clientName: this.clientName(),
157
+ requiredScopes: this.requiredScopes()
129
158
  };
130
159
  return { ...this.scopeInjectionConfig(), data };
131
160
  }, ...(ngDevMode ? [{ debugName: "resolvedScopeInjectionConfig" }] : /* istanbul ignore next */ []));
132
161
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOAuthConsentViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
133
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", 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: `
162
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxFirebaseOAuthConsentViewComponent, isStandalone: true, selector: "dbx-firebase-oauth-consent-view", inputs: { details: { classPropertyName: "details", publicName: "details", isSignal: true, isRequired: false, transformFunction: null }, consentStateCase: { classPropertyName: "consentStateCase", publicName: "consentStateCase", isSignal: true, isRequired: true, transformFunction: null }, scopeInjectionConfig: { classPropertyName: "scopeInjectionConfig", publicName: "scopeInjectionConfig", isSignal: true, isRequired: true, transformFunction: null }, requiredScopes: { classPropertyName: "requiredScopes", publicName: "requiredScopes", isSignal: true, isRequired: false, transformFunction: null }, approveHandler: { classPropertyName: "approveHandler", publicName: "approveHandler", isSignal: true, isRequired: true, transformFunction: null }, denyHandler: { classPropertyName: "denyHandler", publicName: "denyHandler", isSignal: true, isRequired: true, transformFunction: null } }, host: { classAttribute: "d-block dbx-firebase-oauth-consent-view" }, ngImport: i0, template: `
134
163
  <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">
164
+ @switch (consentStateCase()) {
165
+ @case ('unknown') {
166
+ <dbx-basic-loading [loading]="true"></dbx-basic-loading>
167
+ }
168
+ @case ('no_user') {
169
+ <ng-content></ng-content>
170
+ }
171
+ @case ('user') {
172
+ <div class="dbx-firebase-oauth-consent-header">
173
+ @if (clientName()) {
174
+ <h2>You're signing in to {{ clientName() }}</h2>
175
+ }
176
+ <div class="dbx-firebase-oauth-consent-header-info dbx-flex">
177
+ <dbx-avatar [avatarUrl]="logoUri()" [avatarStyle]="'square'" avatarIcon="apps"></dbx-avatar>
178
+ <span>
179
+ @if (clientUri()) {
180
+ <a class="dbx-firebase-oauth-consent-client-uri" [href]="clientUri()" target="_blank" rel="noopener noreferrer">{{ clientUri() }}</a>
181
+ }
182
+ </span>
183
+ </div>
184
+ </div>
139
185
  @if (clientName()) {
140
- <h2>You're signing in to {{ clientName() }}</h2>
186
+ <p class="dbx-firebase-oauth-consent-prompt">
187
+ <strong>{{ clientName() }}</strong>
188
+ is requesting these permissions:
189
+ </p>
141
190
  }
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>
191
+
192
+ <div dbxAction dbxActionSnackbarError [dbxActionHandler]="approveHandler()">
193
+ <dbx-injection [config]="resolvedScopeInjectionConfig()"></dbx-injection>
194
+
195
+ <div class="dbx-pt3 dbx-pb3 dbx-firebase-oauth-consent-actions">
196
+ <dbx-button dbxActionButton text="Approve" [raised]="true" color="primary"></dbx-button>
197
+ <dbx-button-spacer></dbx-button-spacer>
198
+ <ng-container dbxAction dbxActionSnackbarError dbxActionValue [dbxActionHandler]="denyHandler()">
199
+ <dbx-button dbxActionButton text="Deny" [flat]="true" color="warn"></dbx-button>
200
+ </ng-container>
201
+ </div>
149
202
  </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
203
  }
160
204
  }
161
205
  </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", "customContent", "allowClickPropagation", "mode"] }, { kind: "directive", type: DbxButtonSpacerDirective, selector: "dbx-button-spacer,[dbxButtonSpacer]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
206
+ `, 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: DbxBasicLoadingComponent, selector: "dbx-basic-loading", inputs: ["diameter", "mode", "color", "text", "linear", "show", "loading", "error"] }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "customContent", "allowClickPropagation", "mode"] }, { kind: "directive", type: DbxButtonSpacerDirective, selector: "dbx-button-spacer,[dbxButtonSpacer]" }, { kind: "directive", type: DbxActionDirective, selector: "dbx-action,[dbxAction]", exportAs: ["action", "dbxAction"] }, { kind: "directive", type: DbxActionHandlerDirective, selector: "[dbxActionHandler]", inputs: ["dbxActionHandler"] }, { kind: "directive", type: DbxActionValueDirective, selector: "dbxActionValue,[dbxActionValue]", inputs: ["dbxActionValue"] }, { kind: "directive", type: DbxActionButtonDirective, selector: "[dbxActionButton]", inputs: ["dbxActionButtonEcho"] }, { kind: "directive", type: DbxActionSnackbarErrorDirective, selector: "[dbxActionSnackbarError]", inputs: ["dbxActionSnackbarError"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
163
207
  }
164
208
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOAuthConsentViewComponent, decorators: [{
165
209
  type: Component,
166
- args: [{ selector: 'dbx-firebase-oauth-consent-view', standalone: true, imports: [DbxInjectionComponent, DbxAvatarComponent, DbxLoadingComponent, DbxErrorComponent, DbxButtonComponent, DbxButtonSpacerDirective], template: `
210
+ args: [{ selector: 'dbx-firebase-oauth-consent-view', standalone: true, imports: [DbxInjectionComponent, DbxAvatarComponent, DbxBasicLoadingComponent, DbxButtonComponent, DbxButtonSpacerDirective, DbxActionDirective, DbxActionHandlerDirective, DbxActionValueDirective, DbxActionButtonDirective, DbxActionSnackbarErrorDirective], template: `
167
211
  <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">
212
+ @switch (consentStateCase()) {
213
+ @case ('unknown') {
214
+ <dbx-basic-loading [loading]="true"></dbx-basic-loading>
215
+ }
216
+ @case ('no_user') {
217
+ <ng-content></ng-content>
218
+ }
219
+ @case ('user') {
220
+ <div class="dbx-firebase-oauth-consent-header">
221
+ @if (clientName()) {
222
+ <h2>You're signing in to {{ clientName() }}</h2>
223
+ }
224
+ <div class="dbx-firebase-oauth-consent-header-info dbx-flex">
225
+ <dbx-avatar [avatarUrl]="logoUri()" [avatarStyle]="'square'" avatarIcon="apps"></dbx-avatar>
226
+ <span>
227
+ @if (clientUri()) {
228
+ <a class="dbx-firebase-oauth-consent-client-uri" [href]="clientUri()" target="_blank" rel="noopener noreferrer">{{ clientUri() }}</a>
229
+ }
230
+ </span>
231
+ </div>
232
+ </div>
172
233
  @if (clientName()) {
173
- <h2>You're signing in to {{ clientName() }}</h2>
234
+ <p class="dbx-firebase-oauth-consent-prompt">
235
+ <strong>{{ clientName() }}</strong>
236
+ is requesting these permissions:
237
+ </p>
174
238
  }
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>
239
+
240
+ <div dbxAction dbxActionSnackbarError [dbxActionHandler]="approveHandler()">
241
+ <dbx-injection [config]="resolvedScopeInjectionConfig()"></dbx-injection>
242
+
243
+ <div class="dbx-pt3 dbx-pb3 dbx-firebase-oauth-consent-actions">
244
+ <dbx-button dbxActionButton text="Approve" [raised]="true" color="primary"></dbx-button>
245
+ <dbx-button-spacer></dbx-button-spacer>
246
+ <ng-container dbxAction dbxActionSnackbarError dbxActionValue [dbxActionHandler]="denyHandler()">
247
+ <dbx-button dbxActionButton text="Deny" [flat]="true" color="warn"></dbx-button>
248
+ </ng-container>
249
+ </div>
182
250
  </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
251
  }
193
252
  }
194
253
  </div>
195
254
  `, host: {
196
255
  class: 'd-block dbx-firebase-oauth-consent-view'
197
256
  }, 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"] }] } });
257
+ }], propDecorators: { details: [{ type: i0.Input, args: [{ isSignal: true, alias: "details", required: false }] }], consentStateCase: [{ type: i0.Input, args: [{ isSignal: true, alias: "consentStateCase", required: true }] }], scopeInjectionConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "scopeInjectionConfig", required: true }] }], requiredScopes: [{ type: i0.Input, args: [{ isSignal: true, alias: "requiredScopes", required: false }] }], approveHandler: [{ type: i0.Input, args: [{ isSignal: true, alias: "approveHandler", required: true }] }], denyHandler: [{ type: i0.Input, args: [{ isSignal: true, alias: "denyHandler", required: true }] }] } });
199
258
 
200
259
  /**
201
260
  * Abstract base class for consent scope view components.
202
261
  *
203
- * Provides typed access to the `DbxFirebaseOAuthConsentScopesViewData` injected
204
- * via `DBX_INJECTION_COMPONENT_DATA`. Subclasses only need to define a template.
262
+ * Provides typed access to the `DbxFirebaseOAuthConsentScopesViewData`
263
+ * injected via `DBX_INJECTION_COMPONENT_DATA`. Subclasses define the
264
+ * template that renders the requested scopes and (optionally) hosts a
265
+ * forge form decorated with `dbxActionForm`.
205
266
  *
206
267
  * @example
207
268
  * ```typescript
@@ -216,43 +277,200 @@ class AbstractDbxFirebaseOAuthConsentScopeViewComponent {
216
277
  clientName = computed(() => this.data?.clientName ?? '', ...(ngDevMode ? [{ debugName: "clientName" }] : /* istanbul ignore next */ []));
217
278
  clientUri = computed(() => this.data?.details?.client_uri, ...(ngDevMode ? [{ debugName: "clientUri" }] : /* istanbul ignore next */ []));
218
279
  logoUri = computed(() => this.data?.details?.logo_uri, ...(ngDevMode ? [{ debugName: "logoUri" }] : /* istanbul ignore next */ []));
280
+ requiredScopes = computed(() => this.data?.requiredScopes ?? [], ...(ngDevMode ? [{ debugName: "requiredScopes" }] : /* istanbul ignore next */ []));
281
+ isScopeRequired(scope) {
282
+ return this.requiredScopes().includes(scope);
283
+ }
219
284
  }
220
285
 
221
286
  /**
222
- * Standalone presentational component that renders a list of OAuth consent scopes.
287
+ * Selection-list wrapper used as the `listComponentClass` for the OIDC
288
+ * consent scope `dbxForgeListSelectionField`.
289
+ *
290
+ * Reuses the workspace's `dbx-list` selection infrastructure so the existing
291
+ * scope-row visual treatment (name + description) remains intact while the
292
+ * selection state participates in the surrounding `dbxAction`/`dbxActionForm`
293
+ * pipeline.
223
294
  *
224
295
  * @example
225
- * ```html
226
- * <dbx-firebase-oauth-consent-scope-list [scopes]="mappedScopes"></dbx-firebase-oauth-consent-scope-list>
296
+ * ```ts
297
+ * dbxForgeListSelectionField<OAuthConsentScope, DbxFirebaseOAuthConsentScopeListComponent, OidcScope>({
298
+ * key: 'grantedOIDCScopes',
299
+ * props: {
300
+ * listComponentClass: of(DbxFirebaseOAuthConsentScopeListComponent),
301
+ * readKey: (scope) => scope.name,
302
+ * state$: of(successResult(optionalScopes)),
303
+ * wrapped: false,
304
+ * maxHeight: 'none'
305
+ * }
306
+ * });
227
307
  * ```
228
308
  */
229
- class DbxFirebaseOAuthConsentScopeListComponent {
230
- scopes = input([], ...(ngDevMode ? [{ debugName: "scopes" }] : /* istanbul ignore next */ []));
309
+ class DbxFirebaseOAuthConsentScopeListComponent extends AbstractDbxSelectionListWrapperDirective {
310
+ constructor() {
311
+ super({ componentClass: DbxFirebaseOAuthConsentScopeListViewComponent, defaultSelectionMode: 'select' });
312
+ }
231
313
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOAuthConsentScopeListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
232
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", 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>
314
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseOAuthConsentScopeListComponent, isStandalone: true, selector: "dbx-firebase-oauth-consent-scope-list", host: { classAttribute: "dbx-list-no-hover-effects dbx-list-card-items-list" }, usesInheritance: true, ngImport: i0, template: "\n <dbx-list [state]=\"currentState$\" [config]=\"configSignal()\" [hasMore]=\"hasMore()\" [disabled]=\"disabledSignal()\" [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.DbxListComponent, selector: "dbx-list", inputs: ["padded", "state", "config", "disabled", "selectionMode", "hasMore"], outputs: ["contentScrolled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
315
+ }
316
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOAuthConsentScopeListComponent, decorators: [{
317
+ type: Component,
318
+ args: [{
319
+ selector: 'dbx-firebase-oauth-consent-scope-list',
320
+ template: DEFAULT_LIST_WRAPPER_COMPONENT_CONFIGURATION_TEMPLATE,
321
+ host: {
322
+ class: 'dbx-list-no-hover-effects dbx-list-card-items-list'
323
+ },
324
+ imports: [DbxListWrapperComponentImportsModule],
325
+ changeDetection: ChangeDetectionStrategy.OnPush,
326
+ standalone: true
327
+ }]
328
+ }], ctorParameters: () => [] });
329
+ /**
330
+ * Selection list view that pairs with `DbxFirebaseOAuthConsentScopeListComponent`.
331
+ * Maps each `OAuthConsentScope` to a `DbxValueListItem` keyed by the scope name
332
+ * and renders it through `DbxFirebaseOAuthConsentScopeListItemComponent`.
333
+ */
334
+ class DbxFirebaseOAuthConsentScopeListViewComponent extends AbstractDbxSelectionListViewDirective {
335
+ config = {
336
+ componentClass: DbxFirebaseOAuthConsentScopeListItemComponent,
337
+ mapValuesToItemValues: (values) => of(values.map((scope) => ({ ...scope, key: scope.name, itemValue: scope })))
338
+ };
339
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOAuthConsentScopeListViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
340
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseOAuthConsentScopeListViewComponent, isStandalone: true, selector: "dbx-firebase-oauth-consent-scope-list-view", providers: provideDbxListView(DbxFirebaseOAuthConsentScopeListViewComponent), 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.DbxSelectionValueListViewComponent, selector: "dbx-selection-list-view" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
341
+ }
342
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOAuthConsentScopeListViewComponent, decorators: [{
343
+ type: Component,
344
+ args: [{
345
+ selector: 'dbx-firebase-oauth-consent-scope-list-view',
346
+ template: DEFAULT_DBX_SELECTION_VALUE_LIST_COMPONENT_CONFIGURATION_TEMPLATE,
347
+ imports: [DbxSelectionValueListViewComponentImportsModule],
348
+ providers: provideDbxListView(DbxFirebaseOAuthConsentScopeListViewComponent),
349
+ changeDetection: ChangeDetectionStrategy.OnPush,
350
+ standalone: true
351
+ }]
352
+ }] });
353
+ /**
354
+ * Item row inside the OIDC consent scope selection list. Shown as the visual
355
+ * row for both selected and unselected scopes — the selection chrome (the
356
+ * leading checkbox/highlight) is provided by the wrapping
357
+ * `dbx-selection-list-view`.
358
+ */
359
+ class DbxFirebaseOAuthConsentScopeListItemComponent extends AbstractDbxValueListViewItemComponent {
360
+ get name() {
361
+ return this.itemValue.name;
362
+ }
363
+ get description() {
364
+ return this.itemValue.description;
365
+ }
366
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOAuthConsentScopeListItemComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
367
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxFirebaseOAuthConsentScopeListItemComponent, isStandalone: true, selector: "ng-component", usesInheritance: true, ngImport: i0, template: `
368
+ <div class="dbx-list-item-padded dbx-list-item-padded-thick dbx-list-two-line-item">
369
+ <div class="item-left">
370
+ <div class="mat-subtitle-2">{{ name }}</div>
371
+ @if (description) {
372
+ <div class="item-details">{{ description }}</div>
238
373
  }
239
374
  </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 });
375
+ </div>
376
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
242
377
  }
243
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOAuthConsentScopeListComponent, decorators: [{
378
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOAuthConsentScopeListItemComponent, decorators: [{
244
379
  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>
380
+ args: [{
381
+ template: `
382
+ <div class="dbx-list-item-padded dbx-list-item-padded-thick dbx-list-two-line-item">
383
+ <div class="item-left">
384
+ <div class="mat-subtitle-2">{{ name }}</div>
385
+ @if (description) {
386
+ <div class="item-details">{{ description }}</div>
251
387
  }
252
388
  </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 }] }] } });
389
+ </div>
390
+ `,
391
+ standalone: true,
392
+ changeDetection: ChangeDetectionStrategy.OnPush
393
+ }]
394
+ }] });
395
+
396
+ /**
397
+ * Validator key emitted when the user has not selected any optional scope.
398
+ * Surfaces alongside the form's invalid state so the action button stays disabled.
399
+ */
400
+ const OAUTH_CONSENT_SCOPES_REQUIRED_VALIDATOR_KIND = 'mustSelectAtLeastOneScope';
401
+ /**
402
+ * Default message shown when the user has cleared every optional scope.
403
+ */
404
+ const OAUTH_CONSENT_SCOPES_REQUIRED_VALIDATOR_DEFAULT_MESSAGE = 'Select at least one scope to grant.';
405
+ /**
406
+ * Builds a complete `FormConfig` ready to feed into a forge form component.
407
+ *
408
+ * The resulting form has a single `grantedOIDCScopes` field — a
409
+ * `dbxForgeListSelectionField` rendered through
410
+ * `DbxFirebaseOAuthConsentScopeListComponent` (a `dbx-list` selection wrapper).
411
+ * The list renders bare (no Material form-field wrapper) and without the
412
+ * default 300px height cap so it grows to fit the scope list.
413
+ *
414
+ * @param config - The consent scopes form fields configuration.
415
+ * @returns A `FormConfig` whose single field selects an `OidcScope[]` of granted scopes.
416
+ */
417
+ function oauthConsentScopesFormConfig(config) {
418
+ const { optionalScopes, initiallySelected } = config;
419
+ const value = (initiallySelected ?? optionalScopes.map((scope) => scope.name)).slice();
420
+ const optionalScopesArray = optionalScopes.slice();
421
+ const validators = [
422
+ {
423
+ type: 'custom',
424
+ expression: 'fieldValue && fieldValue.length > 0',
425
+ kind: OAUTH_CONSENT_SCOPES_REQUIRED_VALIDATOR_KIND
426
+ }
427
+ ];
428
+ return {
429
+ fields: [
430
+ dbxForgeListSelectionField({
431
+ key: 'grantedOIDCScopes',
432
+ value,
433
+ validators,
434
+ validationMessages: {
435
+ [OAUTH_CONSENT_SCOPES_REQUIRED_VALIDATOR_KIND]: OAUTH_CONSENT_SCOPES_REQUIRED_VALIDATOR_DEFAULT_MESSAGE
436
+ },
437
+ props: {
438
+ listComponentClass: of(DbxFirebaseOAuthConsentScopeListComponent),
439
+ readKey: (scope) => scope.name,
440
+ state$: of(successResult(optionalScopesArray)),
441
+ wrapped: false,
442
+ maxHeight: 'none'
443
+ }
444
+ })
445
+ ]
446
+ };
447
+ }
448
+
449
+ /**
450
+ * Reusable forge form component that renders one checkbox per OIDC scope
451
+ * defined in {@link OAuthConsentScopesFormFieldsConfig}. Required scopes are
452
+ * rendered as checked-and-disabled.
453
+ *
454
+ * Pair with `<dbx-firebase-oauth-consent-scope-default-view>` for the default
455
+ * consent flow, or embed directly in custom consent UIs that supply their
456
+ * own scope/required configuration.
457
+ */
458
+ class DbxFirebaseOAuthConsentScopeFormComponent extends AbstractConfigAsyncForgeFormDirective {
459
+ formConfig$ = this.currentConfig$.pipe(map((config) => (config ? oauthConsentScopesFormConfig(config) : undefined)));
460
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOAuthConsentScopeFormComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
461
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseOAuthConsentScopeFormComponent, isStandalone: true, selector: "dbx-firebase-oauth-consent-scope-form", providers: dbxForgeFormComponentProviders(), usesInheritance: true, ngImport: i0, template: "<dbx-forge></dbx-forge>", isInline: true, dependencies: [{ kind: "ngmodule", type: DbxForgeFormComponentImportsModule }, { kind: "component", type: i1$1.DbxForgeFormComponent, selector: "dbx-forge" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
462
+ }
463
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOAuthConsentScopeFormComponent, decorators: [{
464
+ type: Component,
465
+ args: [{
466
+ selector: 'dbx-firebase-oauth-consent-scope-form',
467
+ template: DBX_FORGE_FORM_COMPONENT_TEMPLATE,
468
+ providers: dbxForgeFormComponentProviders(),
469
+ imports: [DbxForgeFormComponentImportsModule],
470
+ changeDetection: ChangeDetectionStrategy.OnPush,
471
+ standalone: true
472
+ }]
473
+ }] });
256
474
 
257
475
  const DEFAULT_OIDC_AUTHORIZATION_ENDPOINT_PATH = '/oidc/auth';
258
476
  const DEFAULT_OIDC_INTERACTION_ENDPOINT_PATH = '/interaction';
@@ -336,18 +554,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
336
554
  }] });
337
555
 
338
556
  /**
339
- * Default consent scope view component that maps scope names to descriptions
340
- * using the `OidcScopeDetails` from the app-level OIDC configuration.
557
+ * Default consent scope view component.
341
558
  *
342
- * Apps can override this by providing a custom `consentScopeListViewClass`
343
- * in `DbxFirebaseOidcConfig` or `DbxOAuthConsentComponentConfig`.
559
+ * Reads the requested scopes (and required scopes) from the
560
+ * `DBX_INJECTION_COMPONENT_DATA` provided by the parent consent view,
561
+ * resolves human-readable descriptions from the app-level
562
+ * `DbxFirebaseOidcConfigService`, then renders a
563
+ * `DbxFirebaseOAuthConsentScopeFormComponent` with `dbxActionForm` so the
564
+ * form's value participates in the surrounding `dbxAction` (the consent
565
+ * view's outer Approve action).
566
+ *
567
+ * Required scopes are not user-selectable. They are surfaced as an "Always
568
+ * granted" hint above the form because the server enforces them regardless
569
+ * of payload — including them in the selection list would just add noise.
570
+ *
571
+ * Apps can override this default via
572
+ * `DbxFirebaseOidcConfig.consentScopeListViewClass` or
573
+ * `DbxOAuthConsentComponentConfig.consentScopeListViewClass`. Custom views
574
+ * should similarly apply `dbxActionForm` to a forge form whose value matches
575
+ * `OAuthConsentScopesFormValue`.
344
576
  */
345
- class DbxFirebaseOAuthConsentScopeDefaultViewComponent extends AbstractDbxFirebaseOAuthConsentScopeViewComponent {
346
- oidcConfigService = inject(DbxFirebaseOidcConfigService);
577
+ class DbxFirebaseOAuthConsentScopeDefaultViewComponent {
578
+ _oidcConfigService = inject(DbxFirebaseOidcConfigService);
579
+ _data = inject(DBX_INJECTION_COMPONENT_DATA);
347
580
  mappedScopes = computed(() => {
348
- const availableScopes = this.oidcConfigService.availableScopes;
581
+ const availableScopes = this._oidcConfigService.availableScopes;
349
582
  const availableScopeValues = new Set(availableScopes.map((s) => s.value));
350
- const { included: knownScopes, excluded: unknownScopes } = separateValues(this.scopes(), (name) => availableScopeValues.has(name));
583
+ const { included: knownScopes, excluded: unknownScopes } = separateValues(this._data.scopes, (name) => availableScopeValues.has(name));
351
584
  return [
352
585
  ...knownScopes.map((name) => {
353
586
  const details = availableScopes.find((s) => s.value === name);
@@ -356,29 +589,38 @@ class DbxFirebaseOAuthConsentScopeDefaultViewComponent extends AbstractDbxFireba
356
589
  ...unknownScopes.map((name) => ({ name, description: 'unknown' }))
357
590
  ];
358
591
  }, ...(ngDevMode ? [{ debugName: "mappedScopes" }] : /* istanbul ignore next */ []));
359
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOAuthConsentScopeDefaultViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
360
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseOAuthConsentScopeDefaultViewComponent, isStandalone: true, selector: "dbx-firebase-oauth-consent-scope-default-view", usesInheritance: true, ngImport: i0, template: `
361
- <p>
362
- <strong>{{ clientName() }}</strong>
363
- is requesting these permissions:
364
- </p>
365
- <dbx-firebase-oauth-consent-scope-list [scopes]="mappedScopes()"></dbx-firebase-oauth-consent-scope-list>
366
- `, isInline: true, dependencies: [{ kind: "component", type: DbxFirebaseOAuthConsentScopeListComponent, selector: "dbx-firebase-oauth-consent-scope-list", inputs: ["scopes"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
592
+ optionalScopes = computed(() => {
593
+ const requiredSet = new Set(this._data.requiredScopes ?? []);
594
+ return this.mappedScopes().filter((scope) => !requiredSet.has(scope.name));
595
+ }, ...(ngDevMode ? [{ debugName: "optionalScopes" }] : /* istanbul ignore next */ []));
596
+ alwaysGrantedLabel = computed(() => {
597
+ const required = this._data.requiredScopes ?? [];
598
+ return required.length > 0 ? required.join(', ') : null;
599
+ }, ...(ngDevMode ? [{ debugName: "alwaysGrantedLabel" }] : /* istanbul ignore next */ []));
600
+ formFieldsConfig = computed(() => ({
601
+ optionalScopes: this.optionalScopes()
602
+ }), ...(ngDevMode ? [{ debugName: "formFieldsConfig" }] : /* istanbul ignore next */ []));
603
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOAuthConsentScopeDefaultViewComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
604
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxFirebaseOAuthConsentScopeDefaultViewComponent, isStandalone: true, selector: "dbx-firebase-oauth-consent-scope-default-view", ngImport: i0, template: `
605
+ @if (alwaysGrantedLabel(); as label) {
606
+ <p class="dbx-firebase-oauth-consent-always-granted dbx-hint">Always granted: {{ label }}</p>
607
+ }
608
+ <dbx-firebase-oauth-consent-scope-form dbxActionForm [config]="formFieldsConfig()"></dbx-firebase-oauth-consent-scope-form>
609
+ `, isInline: true, dependencies: [{ kind: "component", type: DbxFirebaseOAuthConsentScopeFormComponent, selector: "dbx-firebase-oauth-consent-scope-form" }, { kind: "directive", type: DbxActionFormDirective, selector: "[dbxActionForm]", inputs: ["dbxActionFormDisabledOnWorking", "dbxActionFormIsValid", "dbxActionFormIsEqual", "dbxActionFormIsModified", "dbxActionFormMapValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
367
610
  }
368
611
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOAuthConsentScopeDefaultViewComponent, decorators: [{
369
612
  type: Component,
370
613
  args: [{
371
614
  selector: 'dbx-firebase-oauth-consent-scope-default-view',
372
- standalone: true,
373
- imports: [DbxFirebaseOAuthConsentScopeListComponent],
374
615
  template: `
375
- <p>
376
- <strong>{{ clientName() }}</strong>
377
- is requesting these permissions:
378
- </p>
379
- <dbx-firebase-oauth-consent-scope-list [scopes]="mappedScopes()"></dbx-firebase-oauth-consent-scope-list>
616
+ @if (alwaysGrantedLabel(); as label) {
617
+ <p class="dbx-firebase-oauth-consent-always-granted dbx-hint">Always granted: {{ label }}</p>
618
+ }
619
+ <dbx-firebase-oauth-consent-scope-form dbxActionForm [config]="formFieldsConfig()"></dbx-firebase-oauth-consent-scope-form>
380
620
  `,
381
- changeDetection: ChangeDetectionStrategy.OnPush
621
+ imports: [DbxFirebaseOAuthConsentScopeFormComponent, DbxActionFormDirective],
622
+ changeDetection: ChangeDetectionStrategy.OnPush,
623
+ standalone: true
382
624
  }]
383
625
  }] });
384
626
 
@@ -418,14 +660,23 @@ class DbxFirebaseOidcInteractionService {
418
660
  /**
419
661
  * Submit consent decision to complete the consent interaction.
420
662
  *
421
- * Automatically attaches the current user's Firebase ID token.
663
+ * Automatically attaches the current user's Firebase ID token. When `approved`
664
+ * is true, optional `grants` may be passed to grant only a subset of the
665
+ * requested scopes/claims/resource scopes; the server validates that any
666
+ * subset is contained in the corresponding `missing*` set on the prompt.
667
+ *
668
+ * When `approved` is false, `grants` is ignored (not sent).
422
669
  *
423
670
  * @param uid - The OIDC interaction UID identifying the current consent interaction.
424
671
  * @param approved - Whether the user approved or denied the consent request.
672
+ * @param grants - Optional subset of OIDC scopes / OIDC claims / resource scopes to grant.
425
673
  * @returns Observable that emits the redirect URL from the server response.
426
674
  */
427
- submitConsent(uid, approved) {
428
- return this._authService.idTokenString$.pipe(first(), switchMap((idToken) => this.http.post(`${this.baseUrl}/${uid}/consent`, { idToken, approved })));
675
+ submitConsent(uid, approved, grants) {
676
+ return this._authService.idTokenString$.pipe(first(), switchMap((idToken) => {
677
+ const body = approved && grants ? { idToken, approved, ...grants } : { idToken, approved };
678
+ return this.http.post(`${this.baseUrl}/${uid}/consent`, body);
679
+ }));
429
680
  }
430
681
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOidcInteractionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
431
682
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOidcInteractionService, providedIn: 'root' });
@@ -454,7 +705,7 @@ class DbxFirebaseOAuthLoginComponent {
454
705
  interactionService = inject(DbxFirebaseOidcInteractionService);
455
706
  uidParamReader = dbxRouteParamReaderInstance(this.dbxRouterService, DEFAULT_OIDC_INTERACTION_UID_PARAM_KEY);
456
707
  interactionUid = toSignal(this.uidParamReader.value$);
457
- isLoggedIn = toSignal(this.dbxFirebaseAuthService.isLoggedIn$, { initialValue: false });
708
+ isLoggedIn = toSignal(this.dbxFirebaseAuthService.isLoggedIn$);
458
709
  submitting = signal(false, ...(ngDevMode ? [{ debugName: "submitting" }] : /* istanbul ignore next */ []));
459
710
  errorMessage = signal(null, ...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
460
711
  loginStateCase = computed(() => {
@@ -464,7 +715,11 @@ class DbxFirebaseOAuthLoginComponent {
464
715
  if (this.errorMessage()) {
465
716
  return 'error';
466
717
  }
467
- if (!this.isLoggedIn()) {
718
+ const isLoggedIn = this.isLoggedIn();
719
+ if (isLoggedIn === undefined) {
720
+ return 'unknown';
721
+ }
722
+ if (!isLoggedIn) {
468
723
  return 'no_user';
469
724
  }
470
725
  return 'user';
@@ -530,17 +785,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
530
785
  }]
531
786
  }], ctorParameters: () => [] });
532
787
 
788
+ /**
789
+ * OIDC scopes that cannot be deselected on the consent screen. `openid` is
790
+ * mandatory for any OIDC flow, so the UI shows it as always-granted and the
791
+ * server enforces it regardless of payload.
792
+ */
793
+ const OAUTH_CONSENT_REQUIRED_SCOPES = ['openid'];
533
794
  /**
534
795
  * Container component for the OIDC OAuth consent screen.
535
796
  *
536
- * Manages all state: route param reading, consent submission, and error handling.
537
- * Delegates visual rendering to `DbxFirebaseOAuthConsentViewComponent`.
538
- *
539
797
  * Reads interaction UID and client details from route params (populated by
540
- * the server redirect), then assembles them into `OAuthInteractionLoginDetails`.
798
+ * the server redirect), assembles them into `OAuthInteractionLoginDetails`,
799
+ * and exposes Approve / Deny handlers that drive the view's nested
800
+ * `dbxAction` contexts.
801
+ *
802
+ * Submission progress and error states are owned by the action stores; this
803
+ * container is just routing-glue + handler factories.
804
+ *
805
+ * Supports ng-content projection — any content provided is passed through to
806
+ * the view component for the `'no_user'` state (e.g. an app's login view).
541
807
  */
542
808
  class DbxOAuthConsentComponent {
543
809
  dbxRouterService = inject(DbxRouterService);
810
+ dbxFirebaseAuthService = inject(DbxFirebaseAuthService);
544
811
  interactionService = inject(DbxFirebaseOidcInteractionService);
545
812
  oidcConfigService = inject(DbxFirebaseOidcConfigService);
546
813
  // Config input
@@ -559,6 +826,8 @@ class DbxOAuthConsentComponent {
559
826
  routeClientUri = toSignal(this.clientUriParamReader.value$);
560
827
  routeLogoUri = toSignal(this.logoUriParamReader.value$);
561
828
  routeScopes = toSignal(this.scopesParamReader.value$);
829
+ // Auth state — undefined until Firebase resolves to avoid a flash between 'unknown' → 'no_user'/'user'
830
+ isLoggedIn = toSignal(this.dbxFirebaseAuthService.isLoggedIn$);
562
831
  // Resolved values
563
832
  resolvedInteractionUid = computed(() => this.routeUid(), ...(ngDevMode ? [{ debugName: "resolvedInteractionUid" }] : /* istanbul ignore next */ []));
564
833
  resolvedDetails = computed(() => {
@@ -579,8 +848,21 @@ class DbxOAuthConsentComponent {
579
848
  scopeInjectionConfig = computed(() => ({
580
849
  componentClass: this.config()?.consentScopeListViewClass ?? this.oidcConfigService.consentScopeListViewClass ?? DbxFirebaseOAuthConsentScopeDefaultViewComponent
581
850
  }), ...(ngDevMode ? [{ debugName: "scopeInjectionConfig" }] : /* istanbul ignore next */ []));
582
- loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
583
- error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
851
+ /**
852
+ * Scopes the user cannot deselect. Forwarded to the view, which shows
853
+ * them as a static "Always granted" hint above the selection list.
854
+ */
855
+ requiredScopes = OAUTH_CONSENT_REQUIRED_SCOPES;
856
+ consentStateCase = computed(() => {
857
+ const isLoggedIn = this.isLoggedIn();
858
+ if (isLoggedIn === undefined) {
859
+ return 'unknown';
860
+ }
861
+ if (!isLoggedIn) {
862
+ return 'no_user';
863
+ }
864
+ return 'user';
865
+ }, ...(ngDevMode ? [{ debugName: "consentStateCase" }] : /* istanbul ignore next */ []));
584
866
  ngOnDestroy() {
585
867
  this.interactionUidParamReader.destroy();
586
868
  this.clientIdParamReader.destroy();
@@ -589,37 +871,47 @@ class DbxOAuthConsentComponent {
589
871
  this.logoUriParamReader.destroy();
590
872
  this.scopesParamReader.destroy();
591
873
  }
592
- approve() {
593
- this._submitConsent(true);
594
- }
595
- deny() {
596
- this._submitConsent(false);
597
- }
598
- _submitConsent(approved) {
874
+ /**
875
+ * Handles the Approve action. Pulls the form's selected scope array
876
+ * straight off the form value (it already matches the API field name
877
+ * `grantedOIDCScopes`) and forwards it through `submitConsent`. On a
878
+ * successful response, hard-navigates to the OIDC server's redirect URL.
879
+ */
880
+ handleApprove = (formValue, context) => {
599
881
  const uid = this.resolvedInteractionUid();
600
882
  if (!uid) {
601
- this.error.set('Missing interaction UID');
883
+ context.reject(new Error('Missing interaction UID'));
602
884
  return;
603
885
  }
604
- this.loading.set(true);
605
- this.error.set(null);
606
- this.interactionService.submitConsent(uid, approved).subscribe({
607
- next: (response) => {
608
- this.loading.set(false);
609
- if (response.redirectTo) {
610
- window.location.href = response.redirectTo;
611
- }
612
- },
613
- error: () => {
614
- this.loading.set(false);
615
- this.error.set('Failed to process consent. Please try again.');
886
+ const grantedOIDCScopes = formValue.grantedOIDCScopes;
887
+ context.startWorkingWithObservable(this.interactionService.submitConsent(uid, true, { grantedOIDCScopes }).pipe(tap((response) => {
888
+ if (response.redirectTo) {
889
+ window.location.href = response.redirectTo;
616
890
  }
617
- });
618
- }
891
+ })));
892
+ };
893
+ /**
894
+ * Handles the Deny action. No payload is sent — the server returns
895
+ * `access_denied` to the OAuth client.
896
+ */
897
+ handleDeny = (_value, context) => {
898
+ const uid = this.resolvedInteractionUid();
899
+ if (!uid) {
900
+ context.reject(new Error('Missing interaction UID'));
901
+ return;
902
+ }
903
+ context.startWorkingWithObservable(this.interactionService.submitConsent(uid, false).pipe(tap((response) => {
904
+ if (response.redirectTo) {
905
+ window.location.href = response.redirectTo;
906
+ }
907
+ })));
908
+ };
619
909
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxOAuthConsentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
620
910
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.11", 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: `
621
- <dbx-firebase-oauth-consent-view [details]="resolvedDetails()" [loading]="loading()" [error]="error()" [scopeInjectionConfig]="scopeInjectionConfig()" (approveClick)="approve()" (denyClick)="deny()"></dbx-firebase-oauth-consent-view>
622
- `, 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 });
911
+ <dbx-firebase-oauth-consent-view [details]="resolvedDetails()" [consentStateCase]="consentStateCase()" [scopeInjectionConfig]="scopeInjectionConfig()" [requiredScopes]="requiredScopes" [approveHandler]="handleApprove" [denyHandler]="handleDeny">
912
+ <ng-content />
913
+ </dbx-firebase-oauth-consent-view>
914
+ `, isInline: true, dependencies: [{ kind: "component", type: DbxFirebaseOAuthConsentViewComponent, selector: "dbx-firebase-oauth-consent-view", inputs: ["details", "consentStateCase", "scopeInjectionConfig", "requiredScopes", "approveHandler", "denyHandler"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
623
915
  }
624
916
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxOAuthConsentComponent, decorators: [{
625
917
  type: Component,
@@ -628,7 +920,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
628
920
  standalone: true,
629
921
  imports: [DbxFirebaseOAuthConsentViewComponent],
630
922
  template: `
631
- <dbx-firebase-oauth-consent-view [details]="resolvedDetails()" [loading]="loading()" [error]="error()" [scopeInjectionConfig]="scopeInjectionConfig()" (approveClick)="approve()" (denyClick)="deny()"></dbx-firebase-oauth-consent-view>
923
+ <dbx-firebase-oauth-consent-view [details]="resolvedDetails()" [consentStateCase]="consentStateCase()" [scopeInjectionConfig]="scopeInjectionConfig()" [requiredScopes]="requiredScopes" [approveHandler]="handleApprove" [denyHandler]="handleDeny">
924
+ <ng-content />
925
+ </dbx-firebase-oauth-consent-view>
632
926
  `,
633
927
  host: {
634
928
  class: 'd-block dbx-firebase-oauth-consent'
@@ -856,7 +1150,7 @@ class DbxFirebaseOidcEntryClientForgeFormComponent extends AbstractConfigAsyncFo
856
1150
  });
857
1151
  }));
858
1152
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOidcEntryClientForgeFormComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
859
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseOidcEntryClientForgeFormComponent, isStandalone: true, selector: "dbx-firebase-oidc-client-forge-form", providers: dbxForgeFormComponentProviders(), usesInheritance: true, ngImport: i0, template: "<dbx-forge></dbx-forge>", isInline: true, dependencies: [{ kind: "ngmodule", type: DbxForgeFormComponentImportsModule }, { kind: "component", type: i1.DbxForgeFormComponent, selector: "dbx-forge" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1153
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseOidcEntryClientForgeFormComponent, isStandalone: true, selector: "dbx-firebase-oidc-client-forge-form", providers: dbxForgeFormComponentProviders(), usesInheritance: true, ngImport: i0, template: "<dbx-forge></dbx-forge>", isInline: true, dependencies: [{ kind: "ngmodule", type: DbxForgeFormComponentImportsModule }, { kind: "component", type: i1$1.DbxForgeFormComponent, selector: "dbx-forge" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
860
1154
  }
861
1155
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOidcEntryClientForgeFormComponent, decorators: [{
862
1156
  type: Component,
@@ -878,7 +1172,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
878
1172
  class DbxFirebaseOidcEntryClientTestForgeFormComponent extends AbstractConfigAsyncForgeFormDirective {
879
1173
  formConfig$ = this.currentConfig$.pipe(map((config) => (config ? oidcEntryClientTestForgeFormFields(config) : undefined)));
880
1174
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOidcEntryClientTestForgeFormComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
881
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseOidcEntryClientTestForgeFormComponent, isStandalone: true, selector: "dbx-firebase-oidc-client-test-forge-form", providers: dbxForgeFormComponentProviders(), usesInheritance: true, ngImport: i0, template: "<dbx-forge></dbx-forge>", isInline: true, dependencies: [{ kind: "ngmodule", type: DbxForgeFormComponentImportsModule }, { kind: "component", type: i1.DbxForgeFormComponent, selector: "dbx-forge" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1175
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseOidcEntryClientTestForgeFormComponent, isStandalone: true, selector: "dbx-firebase-oidc-client-test-forge-form", providers: dbxForgeFormComponentProviders(), usesInheritance: true, ngImport: i0, template: "<dbx-forge></dbx-forge>", isInline: true, dependencies: [{ kind: "ngmodule", type: DbxForgeFormComponentImportsModule }, { kind: "component", type: i1$1.DbxForgeFormComponent, selector: "dbx-forge" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
882
1176
  }
883
1177
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOidcEntryClientTestForgeFormComponent, decorators: [{
884
1178
  type: Component,
@@ -892,6 +1186,173 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
892
1186
  }]
893
1187
  }] });
894
1188
 
1189
+ /**
1190
+ * Document store for a single {@link OidcEntry}.
1191
+ */
1192
+ class OidcEntryDocumentStore extends AbstractDbxFirebaseDocumentStore {
1193
+ oidcModelFunctions = inject(OidcModelFunctions);
1194
+ _latestClientSecret$ = completeOnDestroy(new BehaviorSubject(undefined));
1195
+ /**
1196
+ * The client secret from the most recent create operation.
1197
+ *
1198
+ * Only available immediately after creation — the server does not return it again.
1199
+ */
1200
+ latestClientSecret$ = this._latestClientSecret$.asObservable();
1201
+ get latestClientSecret() {
1202
+ return this._latestClientSecret$.value;
1203
+ }
1204
+ constructor() {
1205
+ super({ firestoreCollection: inject(OidcModelFirestoreCollections).oidcEntryCollection });
1206
+ }
1207
+ createClient = firebaseDocumentStoreCreateFunction(this, this.oidcModelFunctions.oidcEntry.createOidcEntry.client, {
1208
+ onResult: (_params, result) => {
1209
+ this._latestClientSecret$.next(result.client_secret);
1210
+ }
1211
+ });
1212
+ updateClient = firebaseDocumentStoreUpdateFunction(this, this.oidcModelFunctions.oidcEntry.updateOidcEntry.client);
1213
+ rotateClientSecret = firebaseDocumentStoreUpdateFunction(this, this.oidcModelFunctions.oidcEntry.updateOidcEntry.rotateClientSecret, {
1214
+ onResult: (_params, result) => {
1215
+ this._latestClientSecret$.next(result.client_secret);
1216
+ }
1217
+ });
1218
+ deleteClient = firebaseDocumentStoreDeleteFunction(this, this.oidcModelFunctions.oidcEntry.deleteOidcEntry.client);
1219
+ deleteToken = firebaseDocumentStoreDeleteFunction(this, this.oidcModelFunctions.oidcEntry.deleteOidcEntry.token);
1220
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OidcEntryDocumentStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1221
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OidcEntryDocumentStore });
1222
+ }
1223
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OidcEntryDocumentStore, decorators: [{
1224
+ type: Injectable
1225
+ }], ctorParameters: () => [] });
1226
+
1227
+ /**
1228
+ * Wrapper list of {@link OidcEntry} Grant rows belonging to the current user.
1229
+ *
1230
+ * Renders one row per Grant — i.e. one row per "app with access to my account" —
1231
+ * with an inline Revoke button that cascades through every grantable token.
1232
+ */
1233
+ class DbxFirebaseOidcEntryGrantListComponent extends AbstractDbxSelectionListWrapperDirective {
1234
+ constructor() {
1235
+ super({
1236
+ componentClass: DbxFirebaseOidcEntryGrantListViewComponent,
1237
+ defaultSelectionMode: 'view'
1238
+ });
1239
+ }
1240
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOidcEntryGrantListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1241
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseOidcEntryGrantListComponent, isStandalone: true, selector: "dbx-firebase-oidc-grant-list", host: { classAttribute: "dbx-list-no-hover-effects dbx-list-card-items-list" }, providers: provideDbxListViewWrapper(DbxFirebaseOidcEntryGrantListComponent), usesInheritance: true, ngImport: i0, template: "\n <dbx-list [state]=\"currentState$\" [config]=\"configSignal()\" [hasMore]=\"hasMore()\" [disabled]=\"disabledSignal()\" [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.DbxListComponent, selector: "dbx-list", inputs: ["padded", "state", "config", "disabled", "selectionMode", "hasMore"], outputs: ["contentScrolled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1242
+ }
1243
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOidcEntryGrantListComponent, decorators: [{
1244
+ type: Component,
1245
+ args: [{
1246
+ selector: 'dbx-firebase-oidc-grant-list',
1247
+ template: DEFAULT_LIST_WRAPPER_COMPONENT_CONFIGURATION_TEMPLATE,
1248
+ providers: provideDbxListViewWrapper(DbxFirebaseOidcEntryGrantListComponent),
1249
+ standalone: true,
1250
+ host: {
1251
+ class: 'dbx-list-no-hover-effects dbx-list-card-items-list'
1252
+ },
1253
+ imports: [DbxListWrapperComponentImportsModule],
1254
+ changeDetection: ChangeDetectionStrategy.OnPush
1255
+ }]
1256
+ }], ctorParameters: () => [] });
1257
+ class DbxFirebaseOidcEntryGrantListViewComponent extends AbstractDbxSelectionListViewDirective {
1258
+ config = {
1259
+ componentClass: DbxFirebaseOidcEntryGrantListViewItemComponent,
1260
+ mapValuesToItemValues: (x) => of(x.map((y, i) => {
1261
+ const id = y.id;
1262
+ return { ...y, key: id ?? `grant_${i}`, itemValue: y };
1263
+ }))
1264
+ };
1265
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOidcEntryGrantListViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1266
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", type: DbxFirebaseOidcEntryGrantListViewComponent, isStandalone: true, selector: "dbx-firebase-oidc-grant-list-view", providers: provideDbxListView(DbxFirebaseOidcEntryGrantListViewComponent), 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.DbxSelectionValueListViewComponent, selector: "dbx-selection-list-view" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1267
+ }
1268
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOidcEntryGrantListViewComponent, decorators: [{
1269
+ type: Component,
1270
+ args: [{
1271
+ selector: 'dbx-firebase-oidc-grant-list-view',
1272
+ template: DEFAULT_DBX_SELECTION_VALUE_LIST_COMPONENT_CONFIGURATION_TEMPLATE,
1273
+ providers: provideDbxListView(DbxFirebaseOidcEntryGrantListViewComponent),
1274
+ standalone: true,
1275
+ imports: [DbxSelectionValueListViewComponentImportsModule],
1276
+ changeDetection: ChangeDetectionStrategy.OnPush
1277
+ }]
1278
+ }] });
1279
+ // MARK: Item
1280
+ /**
1281
+ * Per-row view for a Grant entry. Inline "Revoke" button uses a per-component
1282
+ * {@link OidcEntryDocumentStore} keyed to this entry's id so calling
1283
+ * `deleteToken` invokes the {@link DeleteOidcTokenParams} callModel against
1284
+ * the right document.
1285
+ */
1286
+ class DbxFirebaseOidcEntryGrantListViewItemComponent extends AbstractDbxValueListViewItemComponent {
1287
+ oidcEntryDocumentStore = inject(OidcEntryDocumentStore);
1288
+ clientIdSignal = computed(() => this._payload().clientId ?? '', ...(ngDevMode ? [{ debugName: "clientIdSignal" }] : /* istanbul ignore next */ []));
1289
+ scopeSignal = computed(() => this._payload().openid?.scope ?? null, ...(ngDevMode ? [{ debugName: "scopeSignal" }] : /* istanbul ignore next */ []));
1290
+ expiresAtSignal = computed(() => this.itemValue.expiresAt ?? null, ...(ngDevMode ? [{ debugName: "expiresAtSignal" }] : /* istanbul ignore next */ []));
1291
+ revokeConfirmConfig = {
1292
+ title: 'Revoke access',
1293
+ prompt: 'This app will lose access to your account immediately. Existing access and refresh tokens stop working.',
1294
+ confirmText: 'Revoke'
1295
+ };
1296
+ handleRevoke = (_, context) => {
1297
+ context.startWorkingWithLoadingStateObservable(this.oidcEntryDocumentStore.deleteToken({}));
1298
+ };
1299
+ constructor() {
1300
+ super();
1301
+ const id = this.itemValue.id;
1302
+ if (id) {
1303
+ this.oidcEntryDocumentStore.setId(id);
1304
+ }
1305
+ }
1306
+ _payload() {
1307
+ return this.itemValue.payload ?? {};
1308
+ }
1309
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOidcEntryGrantListViewItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1310
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: DbxFirebaseOidcEntryGrantListViewItemComponent, isStandalone: true, selector: "dbx-firebase-oidc-grant-list-view-item", providers: [OidcEntryDocumentStore], usesInheritance: true, ngImport: i0, template: `
1311
+ <div class="dbx-list-item-padded dbx-list-item-padded-thick dbx-list-two-line-item">
1312
+ <div class="item-left">
1313
+ <span class="item-title">{{ clientIdSignal() }}</span>
1314
+ @if (scopeSignal()) {
1315
+ <span class="item-details">{{ scopeSignal() }}</span>
1316
+ }
1317
+ @if (expiresAtSignal()) {
1318
+ <span class="item-details-footnote">Expires {{ expiresAtSignal() | date: 'medium' }}</span>
1319
+ }
1320
+ </div>
1321
+ <dbx-spacer></dbx-spacer>
1322
+ <div class="item-right">
1323
+ <dbx-button dbxAction [dbxActionHandler]="handleRevoke" [dbxActionConfirm]="revokeConfirmConfig" dbxActionButton text="Revoke" icon="block" color="warn" [raised]="true"></dbx-button>
1324
+ </div>
1325
+ </div>
1326
+ `, isInline: true, dependencies: [{ kind: "directive", type: DbxSpacerDirective, selector: "dbx-spacer, [dbxSpacer]" }, { kind: "component", type: DbxButtonComponent, selector: "dbx-button", inputs: ["bar", "type", "buttonStyle", "color", "spinnerColor", "customButtonColor", "customTextColor", "customSpinnerColor", "basic", "tonal", "raised", "stroked", "flat", "iconOnly", "fab", "customContent", "allowClickPropagation", "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]", inputs: ["dbxActionButtonEcho"] }, { kind: "directive", type: DbxActionConfirmDirective, selector: "[dbxActionConfirm]", inputs: ["dbxActionConfirm", "dbxActionConfirmSkip"] }, { kind: "pipe", type: DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1327
+ }
1328
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOidcEntryGrantListViewItemComponent, decorators: [{
1329
+ type: Component,
1330
+ args: [{
1331
+ selector: 'dbx-firebase-oidc-grant-list-view-item',
1332
+ template: `
1333
+ <div class="dbx-list-item-padded dbx-list-item-padded-thick dbx-list-two-line-item">
1334
+ <div class="item-left">
1335
+ <span class="item-title">{{ clientIdSignal() }}</span>
1336
+ @if (scopeSignal()) {
1337
+ <span class="item-details">{{ scopeSignal() }}</span>
1338
+ }
1339
+ @if (expiresAtSignal()) {
1340
+ <span class="item-details-footnote">Expires {{ expiresAtSignal() | date: 'medium' }}</span>
1341
+ }
1342
+ </div>
1343
+ <dbx-spacer></dbx-spacer>
1344
+ <div class="item-right">
1345
+ <dbx-button dbxAction [dbxActionHandler]="handleRevoke" [dbxActionConfirm]="revokeConfirmConfig" dbxActionButton text="Revoke" icon="block" color="warn" [raised]="true"></dbx-button>
1346
+ </div>
1347
+ </div>
1348
+ `,
1349
+ standalone: true,
1350
+ imports: [DatePipe, DbxSpacerDirective, DbxButtonComponent, DbxActionDirective, DbxActionHandlerDirective, DbxActionButtonDirective, DbxActionConfirmDirective],
1351
+ providers: [OidcEntryDocumentStore],
1352
+ changeDetection: ChangeDetectionStrategy.OnPush
1353
+ }]
1354
+ }], ctorParameters: () => [] });
1355
+
895
1356
  class DbxFirebaseOidcEntryClientListComponent extends AbstractDbxSelectionListWrapperDirective {
896
1357
  constructor() {
897
1358
  super({
@@ -900,7 +1361,7 @@ class DbxFirebaseOidcEntryClientListComponent extends AbstractDbxSelectionListWr
900
1361
  });
901
1362
  }
902
1363
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOidcEntryClientListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
903
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", 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]=\"disabledSignal()\" [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"] }] });
1364
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", 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]=\"disabledSignal()\" [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.DbxListComponent, selector: "dbx-list", inputs: ["padded", "state", "config", "disabled", "selectionMode", "hasMore"], outputs: ["contentScrolled"] }] });
904
1365
  }
905
1366
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOidcEntryClientListComponent, decorators: [{
906
1367
  type: Component,
@@ -918,7 +1379,7 @@ class DbxFirebaseOidcEntryClientListViewComponent extends AbstractDbxSelectionLi
918
1379
  mapValuesToItemValues: (x) => of(x.map((y, i) => ({ ...y, key: `oidc_${i}`, itemValue: y })))
919
1380
  };
920
1381
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOidcEntryClientListViewComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
921
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", 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" }] });
1382
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.11", 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.DbxSelectionValueListViewComponent, selector: "dbx-selection-list-view" }] });
922
1383
  }
923
1384
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOidcEntryClientListViewComponent, decorators: [{
924
1385
  type: Component,
@@ -1016,42 +1477,77 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
1016
1477
  }] });
1017
1478
 
1018
1479
  /**
1019
- * Document store for a single {@link OidcEntry}.
1480
+ * Collection store for querying {@link OidcEntry} documents.
1020
1481
  */
1021
- class OidcEntryDocumentStore extends AbstractDbxFirebaseDocumentStore {
1022
- oidcModelFunctions = inject(OidcModelFunctions);
1023
- _latestClientSecret$ = completeOnDestroy(new BehaviorSubject(undefined));
1024
- /**
1025
- * The client secret from the most recent create operation.
1026
- *
1027
- * Only available immediately after creation — the server does not return it again.
1028
- */
1029
- latestClientSecret$ = this._latestClientSecret$.asObservable();
1030
- get latestClientSecret() {
1031
- return this._latestClientSecret$.value;
1032
- }
1482
+ class OidcEntryCollectionStore extends AbstractDbxFirebaseCollectionStore {
1033
1483
  constructor() {
1034
1484
  super({ firestoreCollection: inject(OidcModelFirestoreCollections).oidcEntryCollection });
1035
1485
  }
1036
- createClient = firebaseDocumentStoreCreateFunction(this, this.oidcModelFunctions.oidcEntry.createOidcEntry.client, {
1037
- onResult: (_params, result) => {
1038
- this._latestClientSecret$.next(result.client_secret);
1039
- }
1040
- });
1041
- updateClient = firebaseDocumentStoreUpdateFunction(this, this.oidcModelFunctions.oidcEntry.updateOidcEntry.client);
1042
- rotateClientSecret = firebaseDocumentStoreUpdateFunction(this, this.oidcModelFunctions.oidcEntry.updateOidcEntry.rotateClientSecret, {
1043
- onResult: (_params, result) => {
1044
- this._latestClientSecret$.next(result.client_secret);
1045
- }
1046
- });
1047
- deleteClient = firebaseDocumentStoreDeleteFunction(this, this.oidcModelFunctions.oidcEntry.deleteOidcEntry.client);
1048
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OidcEntryDocumentStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1049
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OidcEntryDocumentStore });
1486
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OidcEntryCollectionStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1487
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OidcEntryCollectionStore });
1050
1488
  }
1051
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OidcEntryDocumentStore, decorators: [{
1489
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OidcEntryCollectionStore, decorators: [{
1052
1490
  type: Injectable
1053
1491
  }], ctorParameters: () => [] });
1054
1492
 
1493
+ /**
1494
+ * Directive providing a {@link OidcEntryCollectionStore} for querying {@link OidcEntry} documents.
1495
+ */
1496
+ class OidcEntryCollectionStoreDirective extends DbxFirebaseCollectionStoreDirective {
1497
+ constructor() {
1498
+ super(inject(OidcEntryCollectionStore));
1499
+ }
1500
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OidcEntryCollectionStoreDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1501
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.11", type: OidcEntryCollectionStoreDirective, isStandalone: true, selector: "[dbxOidcEntryCollection]", providers: provideDbxFirebaseCollectionStoreDirective(OidcEntryCollectionStoreDirective, OidcEntryCollectionStore), usesInheritance: true, ngImport: i0 });
1502
+ }
1503
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OidcEntryCollectionStoreDirective, decorators: [{
1504
+ type: Directive,
1505
+ args: [{
1506
+ selector: '[dbxOidcEntryCollection]',
1507
+ providers: provideDbxFirebaseCollectionStoreDirective(OidcEntryCollectionStoreDirective, OidcEntryCollectionStore),
1508
+ standalone: true
1509
+ }]
1510
+ }], ctorParameters: () => [] });
1511
+
1512
+ /**
1513
+ * Drop-in container for the "apps with access to my account" management UI.
1514
+ *
1515
+ * Wires a {@link OidcEntryCollectionStoreDirective} to query Grant entries
1516
+ * for the signed-in user, then renders {@link DbxFirebaseOidcEntryGrantListComponent}
1517
+ * with inline Revoke buttons. No inputs — the container resolves the current
1518
+ * user via {@link DbxFirebaseAuthService}.
1519
+ */
1520
+ class DbxFirebaseOidcEntryGrantListContainerComponent {
1521
+ dbxFirebaseAuthService = inject(DbxFirebaseAuthService);
1522
+ oidcEntryCollectionStoreDirective = viewChild(OidcEntryCollectionStoreDirective, ...(ngDevMode ? [{ debugName: "oidcEntryCollectionStoreDirective" }] : /* istanbul ignore next */ []));
1523
+ grantConstraintsSignal = toSignal(this.dbxFirebaseAuthService.currentAuthUser$.pipe(map((user) => (user?.uid ? oidcGrantEntriesByUidQuery(user.uid) : []))));
1524
+ ngOnInit() {
1525
+ const directive = this.oidcEntryCollectionStoreDirective();
1526
+ directive?.setMaxPages(5);
1527
+ directive?.setItemsPerPage(20);
1528
+ }
1529
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOidcEntryGrantListContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1530
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.11", type: DbxFirebaseOidcEntryGrantListContainerComponent, isStandalone: true, selector: "dbx-firebase-oidc-grant-list-container", viewQueries: [{ propertyName: "oidcEntryCollectionStoreDirective", first: true, predicate: OidcEntryCollectionStoreDirective, descendants: true, isSignal: true }], ngImport: i0, template: `
1531
+ <div dbxOidcEntryCollection dbxFirebaseCollectionChange="auto" [constraints]="grantConstraintsSignal()">
1532
+ <dbx-firebase-oidc-grant-list dbxFirebaseCollectionList></dbx-firebase-oidc-grant-list>
1533
+ </div>
1534
+ `, isInline: true, dependencies: [{ kind: "directive", type: OidcEntryCollectionStoreDirective, selector: "[dbxOidcEntryCollection]" }, { kind: "directive", type: DbxFirebaseCollectionListDirective, selector: "[dbxFirebaseCollectionList]" }, { kind: "directive", type: DbxFirebaseCollectionChangeDirective, selector: "[dbxFirebaseCollectionChange]", inputs: ["dbxFirebaseCollectionChange"] }, { kind: "component", type: DbxFirebaseOidcEntryGrantListComponent, selector: "dbx-firebase-oidc-grant-list" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1535
+ }
1536
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: DbxFirebaseOidcEntryGrantListContainerComponent, decorators: [{
1537
+ type: Component,
1538
+ args: [{
1539
+ selector: 'dbx-firebase-oidc-grant-list-container',
1540
+ template: `
1541
+ <div dbxOidcEntryCollection dbxFirebaseCollectionChange="auto" [constraints]="grantConstraintsSignal()">
1542
+ <dbx-firebase-oidc-grant-list dbxFirebaseCollectionList></dbx-firebase-oidc-grant-list>
1543
+ </div>
1544
+ `,
1545
+ standalone: true,
1546
+ imports: [OidcEntryCollectionStoreDirective, DbxFirebaseCollectionListDirective, DbxFirebaseCollectionChangeDirective, DbxFirebaseOidcEntryGrantListComponent],
1547
+ changeDetection: ChangeDetectionStrategy.OnPush
1548
+ }]
1549
+ }], propDecorators: { oidcEntryCollectionStoreDirective: [{ type: i0.ViewChild, args: [i0.forwardRef(() => OidcEntryCollectionStoreDirective), { isSignal: true }] }] } });
1550
+
1055
1551
  /**
1056
1552
  * Container component for creating a new OAuth client.
1057
1553
  *
@@ -1365,39 +1861,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
1365
1861
  }]
1366
1862
  }] });
1367
1863
 
1368
- /**
1369
- * Collection store for querying {@link OidcEntry} documents.
1370
- */
1371
- class OidcEntryCollectionStore extends AbstractDbxFirebaseCollectionStore {
1372
- constructor() {
1373
- super({ firestoreCollection: inject(OidcModelFirestoreCollections).oidcEntryCollection });
1374
- }
1375
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OidcEntryCollectionStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1376
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OidcEntryCollectionStore });
1377
- }
1378
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OidcEntryCollectionStore, decorators: [{
1379
- type: Injectable
1380
- }], ctorParameters: () => [] });
1381
-
1382
- /**
1383
- * Directive providing a {@link OidcEntryCollectionStore} for querying {@link OidcEntry} documents.
1384
- */
1385
- class OidcEntryCollectionStoreDirective extends DbxFirebaseCollectionStoreDirective {
1386
- constructor() {
1387
- super(inject(OidcEntryCollectionStore));
1388
- }
1389
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OidcEntryCollectionStoreDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1390
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.11", type: OidcEntryCollectionStoreDirective, isStandalone: true, selector: "[dbxOidcEntryCollection]", providers: provideDbxFirebaseCollectionStoreDirective(OidcEntryCollectionStoreDirective, OidcEntryCollectionStore), usesInheritance: true, ngImport: i0 });
1391
- }
1392
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: OidcEntryCollectionStoreDirective, decorators: [{
1393
- type: Directive,
1394
- args: [{
1395
- selector: '[dbxOidcEntryCollection]',
1396
- providers: provideDbxFirebaseCollectionStoreDirective(OidcEntryCollectionStoreDirective, OidcEntryCollectionStore),
1397
- standalone: true
1398
- }]
1399
- }], ctorParameters: () => [] });
1400
-
1401
1864
  /**
1402
1865
  * Directive providing a {@link OidcEntryDocumentStore} for accessing a single {@link OidcEntry} document.
1403
1866
  */
@@ -1465,5 +1928,5 @@ function provideDbxFirebaseOidc(config) {
1465
1928
  * Generated bundle index. Do not edit.
1466
1929
  */
1467
1930
 
1468
- 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, DbxFirebaseOidcEntryClientForgeFormComponent, DbxFirebaseOidcEntryClientListComponent, DbxFirebaseOidcEntryClientListViewComponent, DbxFirebaseOidcEntryClientListViewItemClientComponent, DbxFirebaseOidcEntryClientListViewItemComponent, DbxFirebaseOidcEntryClientListViewItemDefaultComponent, DbxFirebaseOidcEntryClientTestComponent, DbxFirebaseOidcEntryClientTestForgeFormComponent, DbxFirebaseOidcEntryClientUpdateComponent, DbxFirebaseOidcEntryClientViewComponent, DbxFirebaseOidcInteractionService, DbxOAuthConsentComponent, OidcEntryCollectionStore, OidcEntryCollectionStoreDirective, OidcEntryDocumentStore, OidcEntryDocumentStoreDirective, oidcClientHomepageUriForgeField, oidcClientJwksUriForgeField, oidcClientLogoUriForgeField, oidcClientNameForgeField, oidcClientRedirectUrisForgeField, oidcClientTestClientIdForgeField, oidcClientTestRedirectUriForgeField, oidcClientTestScopesForgeField, oidcClientTokenEndpointAuthMethodForgeField, oidcEntryClientForgeFormFields, oidcEntryClientTestForgeFormFields, oidcEntryClientUpdateForgeFormFields, provideDbxFirebaseOidc, provideOidcModelFirestoreCollections };
1931
+ 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, DbxFirebaseOAuthConsentScopeFormComponent, DbxFirebaseOAuthConsentScopeListComponent, DbxFirebaseOAuthConsentScopeListItemComponent, DbxFirebaseOAuthConsentScopeListViewComponent, DbxFirebaseOAuthConsentViewComponent, DbxFirebaseOAuthLoginComponent, DbxFirebaseOAuthLoginViewComponent, DbxFirebaseOidcConfig, DbxFirebaseOidcConfigService, DbxFirebaseOidcEntryClientCreateComponent, DbxFirebaseOidcEntryClientForgeFormComponent, DbxFirebaseOidcEntryClientListComponent, DbxFirebaseOidcEntryClientListViewComponent, DbxFirebaseOidcEntryClientListViewItemClientComponent, DbxFirebaseOidcEntryClientListViewItemComponent, DbxFirebaseOidcEntryClientListViewItemDefaultComponent, DbxFirebaseOidcEntryClientTestComponent, DbxFirebaseOidcEntryClientTestForgeFormComponent, DbxFirebaseOidcEntryClientUpdateComponent, DbxFirebaseOidcEntryClientViewComponent, DbxFirebaseOidcEntryGrantListComponent, DbxFirebaseOidcEntryGrantListContainerComponent, DbxFirebaseOidcEntryGrantListViewComponent, DbxFirebaseOidcEntryGrantListViewItemComponent, DbxFirebaseOidcInteractionService, DbxOAuthConsentComponent, OAUTH_CONSENT_SCOPES_REQUIRED_VALIDATOR_DEFAULT_MESSAGE, OAUTH_CONSENT_SCOPES_REQUIRED_VALIDATOR_KIND, OidcEntryCollectionStore, OidcEntryCollectionStoreDirective, OidcEntryDocumentStore, OidcEntryDocumentStoreDirective, oauthConsentScopesFormConfig, oidcClientHomepageUriForgeField, oidcClientJwksUriForgeField, oidcClientLogoUriForgeField, oidcClientNameForgeField, oidcClientRedirectUrisForgeField, oidcClientTestClientIdForgeField, oidcClientTestRedirectUriForgeField, oidcClientTestScopesForgeField, oidcClientTokenEndpointAuthMethodForgeField, oidcEntryClientForgeFormFields, oidcEntryClientTestForgeFormFields, oidcEntryClientUpdateForgeFormFields, provideDbxFirebaseOidc, provideOidcModelFirestoreCollections };
1469
1932
  //# sourceMappingURL=dereekb-dbx-firebase-oidc.mjs.map