ngx-oauth 7.0.1 → 8.0.2

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.
package/README.md CHANGED
@@ -1,215 +1,49 @@
1
- ## Angular OAuth
1
+ # ngx-oauth
2
2
 
3
- > `Ngx-oauth` is a fully **OAuth 2.1** compliant angular library. The library supports all the 4 flows:
4
- > * **resource**
5
- > * **implicit**
6
- > * **authorization code**
7
- > * **client credentials**
3
+ OAuth 2.1 library for Angular 21. Zoneless, signal-based.
8
4
 
9
- > Supports OIDC
5
+ ## Projects
10
6
 
11
- > `PKCE` support for authorization code with code verification
7
+ - `projects/ngx-oauth/` the library
8
+ - `projects/ngx-oauth-sample/` — demo app
12
9
 
13
- ### How to
10
+ ## Commands
14
11
 
15
- Provide a `OAuthConfig` using `provideOAuthConfig` or `provideOAuthConfigFactory`
12
+ | Command | Description |
13
+ |---|---|
14
+ | `npm run build:lib` | Build the library |
15
+ | `npm run build:app` | Build the demo app |
16
+ | `npm start` | Serve demo (`https://localhost:3000`, SSL enabled) |
17
+ | `npm test` | Run library tests (Vitest) |
18
+ | `npm run lint` | Lint both projects |
19
+ | `npm run format` | Format code |
16
20
 
17
- Example:
18
-
19
- ```typescript
20
- const oauthConfig = {
21
- config: {
22
- tokenPath: '/authorizationserver/oauth/token',
23
- revokePath: '/authorizationserver/oauth/revoke', // optional
24
- clientId: '<your_client_id>',
25
- clientSecret: '<your_client_secret>'
26
- },
27
- storage: localStorage, // Optional, default value is localStorage
28
- storageKey: 'token' // Optional, default value is 'token'
29
- };
21
+ ## Configure the demo
30
22
 
31
-
32
- export const appConfig: ApplicationConfig = {
33
- providers: [
34
- provideRouter(routes),
35
- provideClientHydration(),
36
- provideHttpClient(
37
- withFetch(),
38
- withInterceptors([OAuthInterceptor])
39
- ),
40
- provideOAuthConfig(keycloakOpenIDConfig),
41
- ]
42
- };
43
- ```
44
-
45
- Example for **authorization code** flow with `OIDC` and `PKCE`
46
- For public oauth clients `clientSecret` can be removed since is not used
23
+ Edit `projects/ngx-oauth-sample/src/app/app.config.ts`:
47
24
 
48
25
  ```typescript
49
26
  const oauthConfig = {
50
27
  config: {
51
- clientId: '<your_client_id>',
52
- clientSecret: '<your_client_secret>', //not used for public clients
53
- authorizePath: '/o/authorize/',
54
- tokenPath: '/o/token/',
55
- revokePath: '/o/revoke/',
56
- scope: 'openid email profile',
57
- pkce: true
58
- },
59
- }
60
- ```
28
+ // autodiscovery
29
+ issuerPath: 'https://your-idp.com/realms/realm',
30
+ clientId: 'your-client-id',
61
31
 
62
- ***Keycloak*** example for **oidc** with autodiscovery
63
-
64
- ```typescript
65
- const keycloakOpenIDConfig = {
66
- config: {
67
- issuerPath: 'http://localhost:8080/realms/<some-realm>',
68
- clientId: '<your_client_id>',
69
- }
70
- };
71
- ```
32
+ // or manual endpoints
33
+ // authorizePath: '/authorize',
34
+ // tokenPath: '/token',
35
+ // clientId: 'your-client-id',
72
36
 
73
- ***Azure*** example
74
-
75
- ```typescript
76
- const azureOpenIDConfig = {
77
- config: {
78
- issuerPath: 'https://login.microsoftonline.com/common/v2.0', // for common make sure you app has "signInAudience": "AzureADandPersonalMicrosoftAccount",
79
- clientId: '<your_client_id>',
80
- scope: 'openid profile email offline_access',
81
- pkce: true // manually, since is required, but code_challenge_methods_supported is not in openid configuration
82
- }
83
- }
84
- ```
85
-
86
- ***Google*** example
87
-
88
- ```typescript
89
- const googleOpenIDConfig = {
90
- type: OAuthType.AUTHORIZATION_CODE,
91
- config: {
92
- issuerPath: 'https://accounts.google.com',
93
- clientId: '<your_client_id>',
94
- clientSecret: '<your_client_secret>',
95
- scope: 'openid profile email'
96
- }
97
- }
98
- ```
99
-
100
- You can use the `oauth-login` component
101
-
102
- ```html
103
-
104
- <div class="login-component">
105
- <oauth-login></oauth-login>
106
- </div>
107
- ```
108
-
109
- or with params
110
-
111
- ```typescript
112
- @Component({
113
- selector: 'login-component',
114
- template: `
115
- <oauth-login [type]="type"
116
- [i18n]="i18n"
117
- [profileName$]="profileName$"
118
- [useLogoutUrl]="useLogoutUrl"
119
- [(state)]="state"></oauth-login>
120
- `
121
- })
122
- export class LoginComponent {
123
- i18n: OAuthLoginI18n = {
124
- username: 'Username'
125
- };
126
- state = 'some_salt_hash_or_whatever';
127
- // not only revoke tokens but also access the logout page if defined.
128
- // logoutPath needs to be defined. logoutRedirectUri is optional. Current url will be used if undefined
129
- useLogoutUrl = true;
130
-
131
- constructor(private oauthService: OAuthService) {
132
- }
133
-
134
- get profileName$(): Observable<string> {
135
- // ex: get profile name form oidc user_info endpoint or get it from some user service
136
- return this.oauthService.userInfo$.pipe(
137
- map(v => `${v.name}&nbsp;${this.getPicture(v.picture)}`)
138
- );
139
- }
140
-
141
- // show profile picture if user info provides thumbnail url
142
- getPicture(picture?: string) {
143
- return picture && `<img class="rounded-circle img-thumbnail" src="${picture}">` || ''
37
+ scope: 'openid profile email',
38
+ pkce: true
144
39
  }
145
40
  }
146
41
  ```
147
42
 
148
- (`state`, `redirectUri`, `responseType` are optional in case of some advanced configuration)
149
-
150
- or create your custom login template using OAuthService
151
-
152
- ```html
153
-
154
- <div class="login-component">
155
- <oauth-login>
156
- <ng-template #login let-li="login" let-s="status" let-lo="logout">
157
- <form (submit)="li({username: username, password: password})">
158
- <ng-container *ngIf="s === OAuthStatus.AUTHORIZED; else loginTemplate">
159
- <h2>profileName</h2>
160
- <button (click)="lo()">Logout</button>
161
- </ng-container>
162
- <ng-template #loginTemplate>
163
- <div class="card">
164
- <div class="card-header text-center">
165
- <h2 class="m-0 p-3">
166
- <strong>Login</strong>
167
- </h2>
168
- </div>
169
- <div class="card-body">
170
- <div class="mb-3">
171
- <input type="text" class="form-control" name="username" required [(ngModel)]="oauthService.username"
172
- placeholder="username">
173
- </div>
174
- <div class="mb-3">
175
- <input type="password" class="form-control" name="password" required [(ngModel)]="oauthService.password"
176
- placeholder="password">
177
- </div>
178
- </div>
179
- <div class="card-footer">
180
- <div class="text-center">
181
- <button type="submit" class="btn btn-primary">Submit</button>
182
- </div>
183
- </div>
184
- </div>
185
- </ng-template>
186
- </form>
187
- </ng-template>
188
- </oauth-login>
189
- </div>
190
-
191
- ```
192
-
193
- and import OAuthService in your login component constructor
194
-
195
- ## Installing:
196
-
197
- ```
198
- npm install ngx-oauth --save
199
- ```
200
-
201
- ## App Requirements
202
-
203
- * none
204
-
205
- ## Running the demo
43
+ ## Library Usage
206
44
 
207
- * change proxy context in ```proxy.conf.js``` so that vite forwards your request to your oauth server (if relative paths in oauth configuration)
208
- * in app.component.ts add your **clientId, secret**, oauth server **token endpoint** and user **profile endpoint**
209
- * npm i
210
- * npm run build:lib
211
- * npm start
45
+ See [ngx-oauth/README.md](projects/ngx-oauth/README.md) for the full library documentation.
212
46
 
213
- #### Licensing
47
+ ## License
214
48
 
215
- [MIT License](LICENSE)
49
+ [MIT](LICENSE)
@@ -0,0 +1,272 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, PLATFORM_ID, input, viewChild, computed, signal, effect, ViewEncapsulation, Component } from '@angular/core';
3
+ import { isPlatformBrowser, CommonModule } from '@angular/common';
4
+ import * as i1 from '@angular/forms';
5
+ import { FormsModule } from '@angular/forms';
6
+ import * as i2 from '@angular/material/button';
7
+ import { MatButtonModule } from '@angular/material/button';
8
+ import * as i3 from '@angular/material/form-field';
9
+ import { MatFormFieldModule } from '@angular/material/form-field';
10
+ import * as i4 from '@angular/material/icon';
11
+ import { MatIconModule } from '@angular/material/icon';
12
+ import * as i5 from '@angular/material/input';
13
+ import { MatInputModule } from '@angular/material/input';
14
+ import * as i6 from '@angular/material/list';
15
+ import { MatListModule } from '@angular/material/list';
16
+ import * as i7 from '@angular/material/menu';
17
+ import { MatMenuTrigger, MatMenuModule } from '@angular/material/menu';
18
+ import { OAUTH, OAUTH_USER, OAuthStatus, OAuthType } from 'ngx-oauth';
19
+
20
+ const defaultI18n = {
21
+ username: 'Username',
22
+ password: 'Password',
23
+ submit: 'Sign in',
24
+ logout: 'Sign out',
25
+ notAuthorized: 'Sign in',
26
+ authorized: 'Welcome',
27
+ denied: 'Access denied. Try again.',
28
+ dismiss: 'Dismiss',
29
+ showPassword: 'Show password',
30
+ hidePassword: 'Hide password'
31
+ };
32
+ class OAuthLoginComponent {
33
+ constructor() {
34
+ this.oauth = inject(OAUTH);
35
+ this.user = inject(OAUTH_USER);
36
+ this.isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
37
+ this.OAuthStatus = OAuthStatus;
38
+ this.config = input({}, ...(ngDevMode ? [{ debugName: "config" }] : /* istanbul ignore next */ []));
39
+ this.i18n = input(defaultI18n, { ...(ngDevMode ? { debugName: "i18n" } : /* istanbul ignore next */ {}), transform: v => ({ ...defaultI18n, ...v }) });
40
+ this.trigger = viewChild(MatMenuTrigger, ...(ngDevMode ? [{ debugName: "trigger" }] : /* istanbul ignore next */ []));
41
+ this.status = this.oauth.status;
42
+ this.isAuthCode = computed(() => {
43
+ const { responseType } = this.config();
44
+ return responseType && responseType !== OAuthType.RESOURCE;
45
+ }, ...(ngDevMode ? [{ debugName: "isAuthCode" }] : /* istanbul ignore next */ []));
46
+ this.profile = computed(() => {
47
+ const info = this.user.value() ?? {};
48
+ const title = info.name || info.preferred_username || info.email || info.sub || '';
49
+ const subtitle = info.email ?? '';
50
+ const initials = `${info.given_name?.charAt(0) ?? ''}${info.family_name?.charAt(0) ?? ''}`.toUpperCase();
51
+ return { title, subtitle, picture: info.picture, initials };
52
+ }, ...(ngDevMode ? [{ debugName: "profile" }] : /* istanbul ignore next */ []));
53
+ this.visible = signal(false, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
54
+ this.showError = signal(true, ...(ngDevMode ? [{ debugName: "showError" }] : /* istanbul ignore next */ []));
55
+ this.username = '';
56
+ this.password = '';
57
+ effect(() => {
58
+ if (this.status() === OAuthStatus.DENIED)
59
+ this.showError.set(true);
60
+ });
61
+ effect(() => {
62
+ const { username, password } = this.config();
63
+ if (username !== undefined)
64
+ this.username = username;
65
+ if (password !== undefined)
66
+ this.password = password;
67
+ });
68
+ }
69
+ logout() {
70
+ this.trigger()?.closeMenu();
71
+ return this.oauth.logout(this.config().logoutRedirectUri, this.config().state);
72
+ }
73
+ dismissError() {
74
+ this.showError.set(false);
75
+ return this.oauth.logout();
76
+ }
77
+ login(parameters) {
78
+ this.trigger()?.closeMenu();
79
+ return this.oauth.login(parameters);
80
+ }
81
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: OAuthLoginComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
82
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.10", type: OAuthLoginComponent, isStandalone: true, selector: "oauth-login", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, i18n: { classPropertyName: "i18n", publicName: "i18n", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "trigger", first: true, predicate: MatMenuTrigger, descendants: true, isSignal: true }], ngImport: i0, template: `
83
+ @if (isBrowser) {
84
+ @if (status(); as s) {
85
+ @if (s === OAuthStatus.NOT_AUTHORIZED && isAuthCode()) {
86
+ <button matIconButton type="button" [attr.aria-label]="i18n().notAuthorized" (click)="login(config())">
87
+ <mat-icon [fontSet]="'material-icons-outlined'">account_circle</mat-icon>
88
+ </button>
89
+ } @else {
90
+ <button
91
+ matIconButton
92
+ [matMenuTriggerFor]="menu"
93
+ [attr.aria-label]="s === OAuthStatus.AUTHORIZED ? i18n().authorized : i18n().notAuthorized">
94
+ <mat-icon [fontSet]="s === OAuthStatus.AUTHORIZED ? 'material-icons' : 'material-icons-outlined'">account_circle</mat-icon>
95
+ </button>
96
+ <mat-menu #menu="matMenu" xPosition="after">
97
+ <div tabindex="-1" (click)="$event.stopPropagation()" (keydown)="$event.stopPropagation()" class="oauth-login-content">
98
+ @if (s === OAuthStatus.AUTHORIZED) {
99
+ <mat-list class="p-0!">
100
+ <mat-list-item>
101
+ @if (profile().picture) {
102
+ <img matListItemAvatar [src]="profile().picture" alt="" />
103
+ } @else {
104
+ <div
105
+ matListItemAvatar
106
+ class="flex! items-center justify-center bg-blue-600 text-sm font-semibold uppercase text-white">
107
+ {{ profile().initials || '?' }}
108
+ </div>
109
+ }
110
+ <span matListItemTitle>{{ profile().title }}</span>
111
+ @if (profile().subtitle) {
112
+ <span matListItemLine>{{ profile().subtitle }}</span>
113
+ }
114
+ <div matListItemMeta>
115
+ <button mat-icon-button type="button" [attr.aria-label]="i18n().logout" (click)="logout()">
116
+ <mat-icon>close</mat-icon>
117
+ </button>
118
+ </div>
119
+ </mat-list-item>
120
+ </mat-list>
121
+ } @else if (s === OAuthStatus.DENIED && showError()) {
122
+ <mat-list class="p-0!">
123
+ <mat-list-item class="!bg-red-50 text-red-800">
124
+ <mat-icon matListItemIcon class="!text-red-600">error_outline</mat-icon>
125
+ <span matListItemLine class="flex-1 text-sm" [innerHTML]="i18n().denied"></span>
126
+ <button mat-icon-button matListItemMeta type="button" (click)="dismissError()" [attr.aria-label]="i18n().dismiss">
127
+ <mat-icon>close</mat-icon>
128
+ </button>
129
+ </mat-list-item>
130
+ </mat-list>
131
+ } @else {
132
+ <form
133
+ #form="ngForm"
134
+ (ngSubmit)="login({ username: username, password: password })"
135
+ autocomplete="on"
136
+ class="flex flex-col gap-3 m-3">
137
+ <mat-form-field subscriptSizing="dynamic" class="block w-full">
138
+ <mat-label>{{ i18n().username }}</mat-label>
139
+ <mat-icon matPrefix>alternate_email</mat-icon>
140
+ <input matInput name="username" autocomplete="username" required [(ngModel)]="username" />
141
+ </mat-form-field>
142
+ <mat-form-field subscriptSizing="dynamic" class="block w-full">
143
+ <mat-label>{{ i18n().password }}</mat-label>
144
+ <mat-icon matPrefix>password</mat-icon>
145
+ <input
146
+ matInput
147
+ name="password"
148
+ autocomplete="current-password"
149
+ required
150
+ [type]="visible() ? 'text' : 'password'"
151
+ [(ngModel)]="password" />
152
+ <button
153
+ mat-icon-button
154
+ matSuffix
155
+ type="button"
156
+ (click)="visible.set(!visible())"
157
+ [attr.aria-label]="visible() ? i18n().hidePassword : i18n().showPassword">
158
+ <mat-icon>{{ visible() ? 'visibility_off' : 'visibility' }}</mat-icon>
159
+ </button>
160
+ </mat-form-field>
161
+ <div class="flex justify-end pt-1">
162
+ <button mat-flat-button type="submit" [disabled]="form.invalid">{{ i18n().submit }}</button>
163
+ </div>
164
+ </form>
165
+ }
166
+ </div>
167
+ </mat-menu>
168
+ }
169
+ }
170
+ }
171
+ `, isInline: true, styles: [".mat-mdc-menu-panel:has(.oauth-login-content){max-width:none;min-width:360px}.oauth-login-content .mat-mdc-list-item .mdc-list-item__end{align-self:center!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i3.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i3.MatLabel, selector: "mat-label" }, { kind: "directive", type: i3.MatPrefix, selector: "[matPrefix], [matIconPrefix], [matTextPrefix]", inputs: ["matTextPrefix"] }, { kind: "directive", type: i3.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatListModule }, { kind: "component", type: i6.MatList, selector: "mat-list", exportAs: ["matList"] }, { kind: "component", type: i6.MatListItem, selector: "mat-list-item, a[mat-list-item], button[mat-list-item]", inputs: ["activated"], exportAs: ["matListItem"] }, { kind: "directive", type: i6.MatListItemAvatar, selector: "[matListItemAvatar]" }, { kind: "directive", type: i6.MatListItemIcon, selector: "[matListItemIcon]" }, { kind: "directive", type: i6.MatListItemLine, selector: "[matListItemLine]" }, { kind: "directive", type: i6.MatListItemTitle, selector: "[matListItemTitle]" }, { kind: "directive", type: i6.MatListItemMeta, selector: "[matListItemMeta]" }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i7.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "directive", type: i7.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }], encapsulation: i0.ViewEncapsulation.None }); }
172
+ }
173
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: OAuthLoginComponent, decorators: [{
174
+ type: Component,
175
+ args: [{ selector: 'oauth-login', standalone: true, imports: [CommonModule, FormsModule, MatButtonModule, MatFormFieldModule, MatIconModule, MatInputModule, MatListModule, MatMenuModule], template: `
176
+ @if (isBrowser) {
177
+ @if (status(); as s) {
178
+ @if (s === OAuthStatus.NOT_AUTHORIZED && isAuthCode()) {
179
+ <button matIconButton type="button" [attr.aria-label]="i18n().notAuthorized" (click)="login(config())">
180
+ <mat-icon [fontSet]="'material-icons-outlined'">account_circle</mat-icon>
181
+ </button>
182
+ } @else {
183
+ <button
184
+ matIconButton
185
+ [matMenuTriggerFor]="menu"
186
+ [attr.aria-label]="s === OAuthStatus.AUTHORIZED ? i18n().authorized : i18n().notAuthorized">
187
+ <mat-icon [fontSet]="s === OAuthStatus.AUTHORIZED ? 'material-icons' : 'material-icons-outlined'">account_circle</mat-icon>
188
+ </button>
189
+ <mat-menu #menu="matMenu" xPosition="after">
190
+ <div tabindex="-1" (click)="$event.stopPropagation()" (keydown)="$event.stopPropagation()" class="oauth-login-content">
191
+ @if (s === OAuthStatus.AUTHORIZED) {
192
+ <mat-list class="p-0!">
193
+ <mat-list-item>
194
+ @if (profile().picture) {
195
+ <img matListItemAvatar [src]="profile().picture" alt="" />
196
+ } @else {
197
+ <div
198
+ matListItemAvatar
199
+ class="flex! items-center justify-center bg-blue-600 text-sm font-semibold uppercase text-white">
200
+ {{ profile().initials || '?' }}
201
+ </div>
202
+ }
203
+ <span matListItemTitle>{{ profile().title }}</span>
204
+ @if (profile().subtitle) {
205
+ <span matListItemLine>{{ profile().subtitle }}</span>
206
+ }
207
+ <div matListItemMeta>
208
+ <button mat-icon-button type="button" [attr.aria-label]="i18n().logout" (click)="logout()">
209
+ <mat-icon>close</mat-icon>
210
+ </button>
211
+ </div>
212
+ </mat-list-item>
213
+ </mat-list>
214
+ } @else if (s === OAuthStatus.DENIED && showError()) {
215
+ <mat-list class="p-0!">
216
+ <mat-list-item class="!bg-red-50 text-red-800">
217
+ <mat-icon matListItemIcon class="!text-red-600">error_outline</mat-icon>
218
+ <span matListItemLine class="flex-1 text-sm" [innerHTML]="i18n().denied"></span>
219
+ <button mat-icon-button matListItemMeta type="button" (click)="dismissError()" [attr.aria-label]="i18n().dismiss">
220
+ <mat-icon>close</mat-icon>
221
+ </button>
222
+ </mat-list-item>
223
+ </mat-list>
224
+ } @else {
225
+ <form
226
+ #form="ngForm"
227
+ (ngSubmit)="login({ username: username, password: password })"
228
+ autocomplete="on"
229
+ class="flex flex-col gap-3 m-3">
230
+ <mat-form-field subscriptSizing="dynamic" class="block w-full">
231
+ <mat-label>{{ i18n().username }}</mat-label>
232
+ <mat-icon matPrefix>alternate_email</mat-icon>
233
+ <input matInput name="username" autocomplete="username" required [(ngModel)]="username" />
234
+ </mat-form-field>
235
+ <mat-form-field subscriptSizing="dynamic" class="block w-full">
236
+ <mat-label>{{ i18n().password }}</mat-label>
237
+ <mat-icon matPrefix>password</mat-icon>
238
+ <input
239
+ matInput
240
+ name="password"
241
+ autocomplete="current-password"
242
+ required
243
+ [type]="visible() ? 'text' : 'password'"
244
+ [(ngModel)]="password" />
245
+ <button
246
+ mat-icon-button
247
+ matSuffix
248
+ type="button"
249
+ (click)="visible.set(!visible())"
250
+ [attr.aria-label]="visible() ? i18n().hidePassword : i18n().showPassword">
251
+ <mat-icon>{{ visible() ? 'visibility_off' : 'visibility' }}</mat-icon>
252
+ </button>
253
+ </mat-form-field>
254
+ <div class="flex justify-end pt-1">
255
+ <button mat-flat-button type="submit" [disabled]="form.invalid">{{ i18n().submit }}</button>
256
+ </div>
257
+ </form>
258
+ }
259
+ </div>
260
+ </mat-menu>
261
+ }
262
+ }
263
+ }
264
+ `, encapsulation: ViewEncapsulation.None, styles: [".mat-mdc-menu-panel:has(.oauth-login-content){max-width:none;min-width:360px}.oauth-login-content .mat-mdc-list-item .mdc-list-item__end{align-self:center!important}\n"] }]
265
+ }], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], i18n: [{ type: i0.Input, args: [{ isSignal: true, alias: "i18n", required: false }] }], trigger: [{ type: i0.ViewChild, args: [i0.forwardRef(() => MatMenuTrigger), { isSignal: true }] }] } });
266
+
267
+ /**
268
+ * Generated bundle index. Do not edit.
269
+ */
270
+
271
+ export { OAuthLoginComponent };
272
+ //# sourceMappingURL=ngx-oauth-component.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngx-oauth-component.mjs","sources":["../../../projects/ngx-oauth/component/o-auth-login.component.ts","../../../projects/ngx-oauth/component/ngx-oauth-component.ts"],"sourcesContent":["import { Component, computed, effect, inject, input, PLATFORM_ID, signal, viewChild, ViewEncapsulation } from '@angular/core'\nimport { CommonModule, isPlatformBrowser } from '@angular/common'\nimport { FormsModule } from '@angular/forms'\nimport { MatButtonModule } from '@angular/material/button'\nimport { MatFormFieldModule } from '@angular/material/form-field'\nimport { MatIconModule } from '@angular/material/icon'\nimport { MatInputModule } from '@angular/material/input'\nimport { MatListModule } from '@angular/material/list'\nimport { MatMenuModule, MatMenuTrigger } from '@angular/material/menu'\nimport { AuthorizationCodeParameters, OAUTH, OAUTH_USER, OAuthParameters, OAuthStatus, OAuthType, ResourceOwnerParameters } from 'ngx-oauth'\n\nexport type OAuthLoginConfig = Partial<ResourceOwnerParameters & AuthorizationCodeParameters & { logoutRedirectUri: string }>\n\nexport type OAuthLoginI18n = {\n username?: string\n password?: string\n submit?: string\n logout?: string\n notAuthorized?: string\n authorized?: string\n denied?: string\n dismiss?: string\n showPassword?: string\n hidePassword?: string\n}\n\nconst defaultI18n: Required<OAuthLoginI18n> = {\n username: 'Username',\n password: 'Password',\n submit: 'Sign in',\n logout: 'Sign out',\n notAuthorized: 'Sign in',\n authorized: 'Welcome',\n denied: 'Access denied. Try again.',\n dismiss: 'Dismiss',\n showPassword: 'Show password',\n hidePassword: 'Hide password'\n}\n\n@Component({\n selector: 'oauth-login',\n standalone: true,\n imports: [CommonModule, FormsModule, MatButtonModule, MatFormFieldModule, MatIconModule, MatInputModule, MatListModule, MatMenuModule],\n template: `\n @if (isBrowser) {\n @if (status(); as s) {\n @if (s === OAuthStatus.NOT_AUTHORIZED && isAuthCode()) {\n <button matIconButton type=\"button\" [attr.aria-label]=\"i18n().notAuthorized\" (click)=\"login(config())\">\n <mat-icon [fontSet]=\"'material-icons-outlined'\">account_circle</mat-icon>\n </button>\n } @else {\n <button\n matIconButton\n [matMenuTriggerFor]=\"menu\"\n [attr.aria-label]=\"s === OAuthStatus.AUTHORIZED ? i18n().authorized : i18n().notAuthorized\">\n <mat-icon [fontSet]=\"s === OAuthStatus.AUTHORIZED ? 'material-icons' : 'material-icons-outlined'\">account_circle</mat-icon>\n </button>\n <mat-menu #menu=\"matMenu\" xPosition=\"after\">\n <div tabindex=\"-1\" (click)=\"$event.stopPropagation()\" (keydown)=\"$event.stopPropagation()\" class=\"oauth-login-content\">\n @if (s === OAuthStatus.AUTHORIZED) {\n <mat-list class=\"p-0!\">\n <mat-list-item>\n @if (profile().picture) {\n <img matListItemAvatar [src]=\"profile().picture\" alt=\"\" />\n } @else {\n <div\n matListItemAvatar\n class=\"flex! items-center justify-center bg-blue-600 text-sm font-semibold uppercase text-white\">\n {{ profile().initials || '?' }}\n </div>\n }\n <span matListItemTitle>{{ profile().title }}</span>\n @if (profile().subtitle) {\n <span matListItemLine>{{ profile().subtitle }}</span>\n }\n <div matListItemMeta>\n <button mat-icon-button type=\"button\" [attr.aria-label]=\"i18n().logout\" (click)=\"logout()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </mat-list-item>\n </mat-list>\n } @else if (s === OAuthStatus.DENIED && showError()) {\n <mat-list class=\"p-0!\">\n <mat-list-item class=\"!bg-red-50 text-red-800\">\n <mat-icon matListItemIcon class=\"!text-red-600\">error_outline</mat-icon>\n <span matListItemLine class=\"flex-1 text-sm\" [innerHTML]=\"i18n().denied\"></span>\n <button mat-icon-button matListItemMeta type=\"button\" (click)=\"dismissError()\" [attr.aria-label]=\"i18n().dismiss\">\n <mat-icon>close</mat-icon>\n </button>\n </mat-list-item>\n </mat-list>\n } @else {\n <form\n #form=\"ngForm\"\n (ngSubmit)=\"login({ username: username, password: password })\"\n autocomplete=\"on\"\n class=\"flex flex-col gap-3 m-3\">\n <mat-form-field subscriptSizing=\"dynamic\" class=\"block w-full\">\n <mat-label>{{ i18n().username }}</mat-label>\n <mat-icon matPrefix>alternate_email</mat-icon>\n <input matInput name=\"username\" autocomplete=\"username\" required [(ngModel)]=\"username\" />\n </mat-form-field>\n <mat-form-field subscriptSizing=\"dynamic\" class=\"block w-full\">\n <mat-label>{{ i18n().password }}</mat-label>\n <mat-icon matPrefix>password</mat-icon>\n <input\n matInput\n name=\"password\"\n autocomplete=\"current-password\"\n required\n [type]=\"visible() ? 'text' : 'password'\"\n [(ngModel)]=\"password\" />\n <button\n mat-icon-button\n matSuffix\n type=\"button\"\n (click)=\"visible.set(!visible())\"\n [attr.aria-label]=\"visible() ? i18n().hidePassword : i18n().showPassword\">\n <mat-icon>{{ visible() ? 'visibility_off' : 'visibility' }}</mat-icon>\n </button>\n </mat-form-field>\n <div class=\"flex justify-end pt-1\">\n <button mat-flat-button type=\"submit\" [disabled]=\"form.invalid\">{{ i18n().submit }}</button>\n </div>\n </form>\n }\n </div>\n </mat-menu>\n }\n }\n }\n `,\n styles: `\n .mat-mdc-menu-panel:has(.oauth-login-content) {\n max-width: none;\n min-width: 360px;\n }\n .oauth-login-content .mat-mdc-list-item .mdc-list-item__end {\n align-self: center !important;\n }\n `,\n encapsulation: ViewEncapsulation.None\n})\nexport class OAuthLoginComponent {\n private oauth = inject(OAUTH)\n private user = inject(OAUTH_USER)\n protected readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID))\n readonly OAuthStatus = OAuthStatus\n readonly config = input<OAuthLoginConfig>({})\n readonly i18n = input<Required<OAuthLoginI18n>, OAuthLoginI18n | undefined>(defaultI18n, {\n transform: v => ({ ...defaultI18n, ...v })\n })\n readonly trigger = viewChild(MatMenuTrigger)\n protected readonly status = this.oauth.status\n protected readonly isAuthCode = computed(() => {\n const { responseType } = this.config()\n return responseType && responseType !== OAuthType.RESOURCE\n })\n protected readonly profile = computed(() => {\n const info = this.user.value() ?? {}\n const title = info.name || info.preferred_username || info.email || info.sub || ''\n const subtitle = info.email ?? ''\n const initials = `${info.given_name?.charAt(0) ?? ''}${info.family_name?.charAt(0) ?? ''}`.toUpperCase()\n return { title, subtitle, picture: info.picture, initials }\n })\n protected readonly visible = signal(false)\n protected readonly showError = signal(true)\n username = ''\n password = ''\n\n constructor() {\n effect(() => {\n if (this.status() === OAuthStatus.DENIED) this.showError.set(true)\n })\n effect(() => {\n const { username, password } = this.config()\n if (username !== undefined) this.username = username\n if (password !== undefined) this.password = password\n })\n }\n\n logout() {\n this.trigger()?.closeMenu()\n return this.oauth.logout(this.config().logoutRedirectUri, this.config().state)\n }\n\n dismissError() {\n this.showError.set(false)\n return this.oauth.logout()\n }\n\n login(parameters: OAuthLoginConfig) {\n this.trigger()?.closeMenu()\n return this.oauth.login(parameters as OAuthParameters)\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}