tokenidp-angular 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +167 -0
- package/fesm2022/tokenidp-angular.mjs +773 -0
- package/fesm2022/tokenidp-angular.mjs.map +1 -0
- package/package.json +48 -0
- package/types/tokenidp-angular.d.ts +138 -0
package/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# TokenIDP Angular SDK
|
|
2
|
+
|
|
3
|
+
Official Angular SDK for integrating TokenIDP OAuth and OpenID Connect authentication into Angular applications.
|
|
4
|
+
|
|
5
|
+
This SDK handles:
|
|
6
|
+
|
|
7
|
+
- OAuth 2.1 Authorization Code + PKCE flow
|
|
8
|
+
- Token management for access, refresh, and ID tokens
|
|
9
|
+
- Login and logout helpers
|
|
10
|
+
- Auth state through Angular signals
|
|
11
|
+
- Automatic token refresh
|
|
12
|
+
- Tenant propagation matching the React SDK
|
|
13
|
+
|
|
14
|
+
## Version
|
|
15
|
+
|
|
16
|
+
This package targets Angular `21.2.x`, the latest stable Angular line verified from npm as `@angular/core@21.2.13`.
|
|
17
|
+
|
|
18
|
+
Angular 21 requires Node.js `^20.19.0 || ^22.12.0 || ^24.0.0`, TypeScript `>=5.9.0 <6.0.0`, and RxJS `^6.5.3 || ^7.4.0`.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install tokenidp-angular
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
For local development from this repository:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cd sdks/angular-idp-sdk
|
|
30
|
+
npm install
|
|
31
|
+
npm run pack
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Then install the generated `.tgz` in an Angular app.
|
|
35
|
+
|
|
36
|
+
## Configure Auth
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { ApplicationConfig } from '@angular/core';
|
|
40
|
+
import { provideRouter, Routes } from '@angular/router';
|
|
41
|
+
import {
|
|
42
|
+
IdpAuthCallbackComponent,
|
|
43
|
+
IdpLoginComponent,
|
|
44
|
+
provideTokenIdpAuth,
|
|
45
|
+
} from 'tokenidp-angular';
|
|
46
|
+
|
|
47
|
+
const routes: Routes = [
|
|
48
|
+
{ path: 'login', component: IdpLoginComponent },
|
|
49
|
+
{ path: 'callback', component: IdpAuthCallbackComponent },
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
export const appConfig: ApplicationConfig = {
|
|
53
|
+
providers: [
|
|
54
|
+
provideRouter(routes),
|
|
55
|
+
provideTokenIdpAuth({
|
|
56
|
+
authority: 'https://idp.example.com',
|
|
57
|
+
clientId: 'angular-client',
|
|
58
|
+
redirectUri: `${window.location.origin}/callback`,
|
|
59
|
+
postLoginRedirectUri: '/dashboard',
|
|
60
|
+
postLogoutRedirectUri: '/login?logged_out=1',
|
|
61
|
+
scope: 'openid profile offline_access',
|
|
62
|
+
storage: 'localStorage',
|
|
63
|
+
}),
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Required values:
|
|
69
|
+
|
|
70
|
+
- `authority`
|
|
71
|
+
- `clientId`
|
|
72
|
+
- `redirectUri`
|
|
73
|
+
|
|
74
|
+
Common optional values:
|
|
75
|
+
|
|
76
|
+
- `scope`, default `openid profile offline_access`
|
|
77
|
+
- `audience`
|
|
78
|
+
- `postLoginRedirectUri`, default `/`
|
|
79
|
+
- `postLogoutRedirectUri`, default `/login`
|
|
80
|
+
- `storage`, one of `memory`, `sessionStorage`, or `localStorage`
|
|
81
|
+
- `autoRefresh`, default `true`
|
|
82
|
+
- `refreshSkewSeconds`, default `180`
|
|
83
|
+
|
|
84
|
+
## Use Auth State
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { Component, inject } from '@angular/core';
|
|
88
|
+
import { TokenIdpAuthService } from 'tokenidp-angular';
|
|
89
|
+
|
|
90
|
+
@Component({
|
|
91
|
+
standalone: true,
|
|
92
|
+
selector: 'app-login-button',
|
|
93
|
+
template: `
|
|
94
|
+
@if (auth.isAuthenticated()) {
|
|
95
|
+
<button type="button" (click)="auth.logout()">Logout</button>
|
|
96
|
+
} @else {
|
|
97
|
+
<button type="button" (click)="auth.login()">Login</button>
|
|
98
|
+
}
|
|
99
|
+
`,
|
|
100
|
+
})
|
|
101
|
+
export class LoginButtonComponent {
|
|
102
|
+
readonly auth = inject(TokenIdpAuthService);
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The service exposes Angular signals for:
|
|
107
|
+
|
|
108
|
+
- `state`
|
|
109
|
+
- `isAuthenticated`
|
|
110
|
+
- `accessToken`
|
|
111
|
+
- `refreshToken`
|
|
112
|
+
- `idToken`
|
|
113
|
+
- `expiresAt`
|
|
114
|
+
- `tenantKey`
|
|
115
|
+
- `error`
|
|
116
|
+
- `landingPage`
|
|
117
|
+
|
|
118
|
+
It also exposes `login(options)`, `logout()`, `handleCallback(params)`, `refresh()`, and `setError(message)`.
|
|
119
|
+
|
|
120
|
+
## Built-In Login Component
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { IdpLoginComponent } from 'tokenidp-angular';
|
|
124
|
+
|
|
125
|
+
// Route directly to IdpLoginComponent, or use it in a template:
|
|
126
|
+
// <tokenidp-login title="Redirecting to TokenIDP..." />
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Callback Route
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { IdpAuthCallbackComponent } from 'tokenidp-angular';
|
|
133
|
+
|
|
134
|
+
// Route /callback to IdpAuthCallbackComponent.
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Tenant Options
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
provideTokenIdpAuth({
|
|
141
|
+
authority: 'https://idp.example.com',
|
|
142
|
+
clientId: 'angular-client',
|
|
143
|
+
redirectUri: `${window.location.origin}/callback`,
|
|
144
|
+
tenantKey: 'tenant-a',
|
|
145
|
+
tenantPropagationMode: 'all', // all | api | none
|
|
146
|
+
tenantQueryParameter: 'tenant',
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Tenant propagation modes:
|
|
151
|
+
|
|
152
|
+
- `all`: add tenant query parameter to hosted authorization and built-in auth endpoint calls
|
|
153
|
+
- `api`: resolve and persist tenant for application API usage without adding tenant to hosted auth endpoint calls
|
|
154
|
+
- `none`: do not resolve or propagate tenant automatically
|
|
155
|
+
|
|
156
|
+
The SDK resolves tenant key from explicit login options, provider config, the current URL query parameter, or session storage.
|
|
157
|
+
|
|
158
|
+
## Logout
|
|
159
|
+
|
|
160
|
+
`logout()` attempts to revoke the refresh token, clears the local session, and redirects to the TokenIDP logout endpoint with `client_id` and `post_logout_redirect_uri`.
|
|
161
|
+
|
|
162
|
+
## Notes
|
|
163
|
+
|
|
164
|
+
- PKCE uses `S256`.
|
|
165
|
+
- OAuth state is stored in `sessionStorage` and validated during callback handling.
|
|
166
|
+
- Refresh uses the configured refresh token and keeps the previous refresh token if the server does not return a rotated value.
|
|
167
|
+
- The SDK stores tokens according to the configured storage mode. Use `sessionStorage` or `memory` for browser clients unless the application explicitly accepts the persistence tradeoff of `localStorage`.
|
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
import * as i1 from '@angular/common';
|
|
2
|
+
import { DOCUMENT, isPlatformBrowser, CommonModule } from '@angular/common';
|
|
3
|
+
import * as i0 from '@angular/core';
|
|
4
|
+
import { InjectionToken, makeEnvironmentProviders, inject, PLATFORM_ID, signal, computed, Injectable, Input, Component } from '@angular/core';
|
|
5
|
+
import { Router } from '@angular/router';
|
|
6
|
+
|
|
7
|
+
const defaultAuthConfig = {
|
|
8
|
+
authority: '',
|
|
9
|
+
clientId: '',
|
|
10
|
+
tenantKey: '',
|
|
11
|
+
tenantPropagationMode: 'all',
|
|
12
|
+
tenantQueryParameter: 'tenant',
|
|
13
|
+
tenantHeaderName: 'X-Tenant-Key',
|
|
14
|
+
tenantKeyStorageKey: 'idp_tenant_key',
|
|
15
|
+
redirectUri: '',
|
|
16
|
+
postLoginRedirectUri: '/',
|
|
17
|
+
postLogoutRedirectUri: '/login',
|
|
18
|
+
scope: 'openid profile offline_access',
|
|
19
|
+
audience: '',
|
|
20
|
+
authorizePath: '/authorize',
|
|
21
|
+
tokenPath: '/token',
|
|
22
|
+
revokePath: '/revoke',
|
|
23
|
+
logoutPath: '/logout',
|
|
24
|
+
storage: 'sessionStorage',
|
|
25
|
+
storageKey: 'idp_user',
|
|
26
|
+
pkceVerifierKey: 'idp_pkce_verifier',
|
|
27
|
+
oauthStateKey: 'idp_oauth_state',
|
|
28
|
+
autoRefresh: true,
|
|
29
|
+
refreshSkewSeconds: 180,
|
|
30
|
+
};
|
|
31
|
+
const TOKEN_IDP_AUTH_CONFIG = new InjectionToken('TOKEN_IDP_AUTH_CONFIG', {
|
|
32
|
+
factory: () => defaultAuthConfig,
|
|
33
|
+
});
|
|
34
|
+
function provideTokenIdpAuth(config) {
|
|
35
|
+
return makeEnvironmentProviders([
|
|
36
|
+
{
|
|
37
|
+
provide: TOKEN_IDP_AUTH_CONFIG,
|
|
38
|
+
useValue: {
|
|
39
|
+
...defaultAuthConfig,
|
|
40
|
+
...config,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
]);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function normalizeTenantPropagationMode(value) {
|
|
47
|
+
const normalized = String(value || 'all').trim().toLowerCase();
|
|
48
|
+
if (normalized === 'none' || normalized === 'api') {
|
|
49
|
+
return normalized;
|
|
50
|
+
}
|
|
51
|
+
return 'all';
|
|
52
|
+
}
|
|
53
|
+
function getAuthTenantKey(config) {
|
|
54
|
+
return normalizeTenantPropagationMode(config.tenantPropagationMode) === 'all'
|
|
55
|
+
? String(config.tenantKey || '').trim()
|
|
56
|
+
: '';
|
|
57
|
+
}
|
|
58
|
+
function getApiTenantKey(config) {
|
|
59
|
+
const mode = normalizeTenantPropagationMode(config.tenantPropagationMode);
|
|
60
|
+
return mode === 'all' || mode === 'api' ? String(config.tenantKey || '').trim() : '';
|
|
61
|
+
}
|
|
62
|
+
function resolveRawTenantKey(config, overrides = {}, locationSearch = '') {
|
|
63
|
+
const explicitTenantKey = String(overrides.tenantKey || config.tenantKey || '').trim();
|
|
64
|
+
if (explicitTenantKey) {
|
|
65
|
+
return explicitTenantKey;
|
|
66
|
+
}
|
|
67
|
+
const tenantFromQuery = new URLSearchParams(locationSearch).get(config.tenantQueryParameter || 'tenant');
|
|
68
|
+
if (tenantFromQuery) {
|
|
69
|
+
return tenantFromQuery.trim();
|
|
70
|
+
}
|
|
71
|
+
const tenantFromStorage = globalThis.sessionStorage?.getItem(config.tenantKeyStorageKey || 'idp_tenant_key');
|
|
72
|
+
if (tenantFromStorage) {
|
|
73
|
+
return tenantFromStorage.trim();
|
|
74
|
+
}
|
|
75
|
+
return '';
|
|
76
|
+
}
|
|
77
|
+
function resolveAuthTenantKey(config, overrides = {}, locationSearch = '') {
|
|
78
|
+
return normalizeTenantPropagationMode(config.tenantPropagationMode) === 'all'
|
|
79
|
+
? resolveRawTenantKey(config, overrides, locationSearch)
|
|
80
|
+
: '';
|
|
81
|
+
}
|
|
82
|
+
function resolveApiTenantKey(config, overrides = {}, locationSearch = '') {
|
|
83
|
+
const mode = normalizeTenantPropagationMode(config.tenantPropagationMode);
|
|
84
|
+
return mode === 'all' || mode === 'api' ? resolveRawTenantKey(config, overrides, locationSearch) : '';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function httpPostJson(url, body) {
|
|
88
|
+
const res = await fetch(url, {
|
|
89
|
+
method: 'POST',
|
|
90
|
+
headers: { 'Content-Type': 'application/json' },
|
|
91
|
+
body: JSON.stringify(body),
|
|
92
|
+
});
|
|
93
|
+
const data = await readResponseBody(res);
|
|
94
|
+
if (!res.ok) {
|
|
95
|
+
throw buildHttpError(data, res.status);
|
|
96
|
+
}
|
|
97
|
+
return data;
|
|
98
|
+
}
|
|
99
|
+
async function readResponseBody(res) {
|
|
100
|
+
const text = await res.text();
|
|
101
|
+
if (!text) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
return JSON.parse(text);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return text;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function buildHttpError(data, status) {
|
|
112
|
+
const err = new Error(getErrorMessage(data, status));
|
|
113
|
+
err.status = status;
|
|
114
|
+
err.data = data;
|
|
115
|
+
return err;
|
|
116
|
+
}
|
|
117
|
+
function getErrorMessage(data, status) {
|
|
118
|
+
if (!data || typeof data !== 'object') {
|
|
119
|
+
return `HTTP ${status}`;
|
|
120
|
+
}
|
|
121
|
+
const record = data;
|
|
122
|
+
const value = record['value'];
|
|
123
|
+
const directError = record['error'];
|
|
124
|
+
const wrappedError = directError && typeof directError === 'object'
|
|
125
|
+
? directError
|
|
126
|
+
: value?.['error'] && typeof value['error'] === 'object'
|
|
127
|
+
? value['error']
|
|
128
|
+
: null;
|
|
129
|
+
return (stringValue(record['error_description']) ||
|
|
130
|
+
stringValue(wrappedError?.['error']) ||
|
|
131
|
+
stringValue(wrappedError?.['Error']) ||
|
|
132
|
+
stringValue(wrappedError?.['message']) ||
|
|
133
|
+
stringValue(wrappedError?.['Message']) ||
|
|
134
|
+
stringValue(directError) ||
|
|
135
|
+
stringValue(record['message']) ||
|
|
136
|
+
`HTTP ${status}`);
|
|
137
|
+
}
|
|
138
|
+
function stringValue(value) {
|
|
139
|
+
return typeof value === 'string' ? value : '';
|
|
140
|
+
}
|
|
141
|
+
function withTenant(url, config, target = 'api') {
|
|
142
|
+
const tenantKey = target === 'auth' ? getAuthTenantKey(config) : getApiTenantKey(config);
|
|
143
|
+
if (!tenantKey) {
|
|
144
|
+
return url;
|
|
145
|
+
}
|
|
146
|
+
const tenantUrl = new URL(url);
|
|
147
|
+
tenantUrl.searchParams.set(config.tenantQueryParameter || 'tenant', tenantKey);
|
|
148
|
+
return tenantUrl.toString();
|
|
149
|
+
}
|
|
150
|
+
function extractToken(tokenPayload) {
|
|
151
|
+
if (!tokenPayload || typeof tokenPayload !== 'object') {
|
|
152
|
+
return { accessToken: '', refreshToken: '', expiresIn: 0, idToken: '' };
|
|
153
|
+
}
|
|
154
|
+
const record = tokenPayload;
|
|
155
|
+
const value = (record['value'] || record);
|
|
156
|
+
const accessToken = stringValue(value['accessToken']) || stringValue(value['access_token']);
|
|
157
|
+
const refreshToken = stringValue(value['refreshToken']) || stringValue(value['refresh_token']);
|
|
158
|
+
const expiresIn = Number(value['expiresIn'] || value['expires_in'] || 0) || 0;
|
|
159
|
+
const idToken = stringValue(value['idToken']) ||
|
|
160
|
+
stringValue(value['id_token']) ||
|
|
161
|
+
stringValue(record['idToken']) ||
|
|
162
|
+
stringValue(record['id_token']);
|
|
163
|
+
return { accessToken, refreshToken, expiresIn, idToken };
|
|
164
|
+
}
|
|
165
|
+
async function exchangeAuthorizationCode(config, payload) {
|
|
166
|
+
const url = withTenant(config.authority + (config.tokenPath || '/token'), config, 'auth');
|
|
167
|
+
return httpPostJson(url, payload);
|
|
168
|
+
}
|
|
169
|
+
async function refreshWithToken(config, payload) {
|
|
170
|
+
const url = withTenant(config.authority + (config.tokenPath || '/token'), config, 'auth');
|
|
171
|
+
return httpPostJson(url, payload);
|
|
172
|
+
}
|
|
173
|
+
async function revokeToken(config, params) {
|
|
174
|
+
if (!config.authority || !params.accessToken || !params.token) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
const url = withTenant(new URL(config.revokePath || '/revoke', config.authority).toString(), config, 'auth');
|
|
178
|
+
const res = await fetch(url, {
|
|
179
|
+
method: 'DELETE',
|
|
180
|
+
headers: {
|
|
181
|
+
Authorization: `Bearer ${params.accessToken}`,
|
|
182
|
+
'Content-Type': 'application/json',
|
|
183
|
+
},
|
|
184
|
+
body: JSON.stringify({
|
|
185
|
+
token: params.token,
|
|
186
|
+
reasonRevoked: params.reasonRevoked || 'logout',
|
|
187
|
+
}),
|
|
188
|
+
});
|
|
189
|
+
const data = await readResponseBody(res);
|
|
190
|
+
if (!res.ok) {
|
|
191
|
+
throw buildHttpError(data, res.status);
|
|
192
|
+
}
|
|
193
|
+
return data;
|
|
194
|
+
}
|
|
195
|
+
function buildLogoutUrl(config, origin = '') {
|
|
196
|
+
if (!config.authority) {
|
|
197
|
+
return '';
|
|
198
|
+
}
|
|
199
|
+
const url = new URL(withTenant(new URL(config.logoutPath || '/logout', config.authority).toString(), config, 'auth'));
|
|
200
|
+
if (config.clientId) {
|
|
201
|
+
url.searchParams.set('client_id', config.clientId);
|
|
202
|
+
}
|
|
203
|
+
const postLogoutRedirectUri = resolvePostLogoutRedirectUri(config, origin);
|
|
204
|
+
if (postLogoutRedirectUri) {
|
|
205
|
+
url.searchParams.set('post_logout_redirect_uri', postLogoutRedirectUri);
|
|
206
|
+
}
|
|
207
|
+
return url.toString();
|
|
208
|
+
}
|
|
209
|
+
function resolvePostLogoutRedirectUri(config, origin) {
|
|
210
|
+
const candidate = config.postLogoutRedirectUri;
|
|
211
|
+
if (!candidate) {
|
|
212
|
+
return '';
|
|
213
|
+
}
|
|
214
|
+
if (origin) {
|
|
215
|
+
return new URL(candidate, origin).toString();
|
|
216
|
+
}
|
|
217
|
+
if (config.redirectUri) {
|
|
218
|
+
return new URL(candidate, config.redirectUri).toString();
|
|
219
|
+
}
|
|
220
|
+
return String(candidate);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function randomState(length = 32) {
|
|
224
|
+
const bytes = new Uint8Array(length);
|
|
225
|
+
crypto.getRandomValues(bytes);
|
|
226
|
+
return Array.from(bytes)
|
|
227
|
+
.map((byte) => byte.toString(16).padStart(2, '0'))
|
|
228
|
+
.join('');
|
|
229
|
+
}
|
|
230
|
+
function buildAuthorizeUrl(config, params) {
|
|
231
|
+
const url = new URL(config.authority + (config.authorizePath || '/authorize'));
|
|
232
|
+
const tenantKey = getAuthTenantKey({
|
|
233
|
+
...config,
|
|
234
|
+
tenantPropagationMode: normalizeTenantPropagationMode(config.tenantPropagationMode),
|
|
235
|
+
tenantKey: params.tenantKey || config.tenantKey,
|
|
236
|
+
});
|
|
237
|
+
url.searchParams.set('response_type', 'code');
|
|
238
|
+
url.searchParams.set('client_id', config.clientId);
|
|
239
|
+
url.searchParams.set('redirect_uri', config.redirectUri);
|
|
240
|
+
url.searchParams.set('scope', config.scope || 'openid profile offline_access');
|
|
241
|
+
url.searchParams.set('code_challenge', params.codeChallenge);
|
|
242
|
+
url.searchParams.set('code_challenge_method', 'S256');
|
|
243
|
+
url.searchParams.set('state', params.state);
|
|
244
|
+
if (params.audience || config.audience) {
|
|
245
|
+
url.searchParams.set('audience', params.audience || config.audience || '');
|
|
246
|
+
}
|
|
247
|
+
if (tenantKey) {
|
|
248
|
+
url.searchParams.set(config.tenantQueryParameter || 'tenant', tenantKey);
|
|
249
|
+
}
|
|
250
|
+
if (params.prompt) {
|
|
251
|
+
url.searchParams.set('prompt', params.prompt);
|
|
252
|
+
}
|
|
253
|
+
if (params.loginHint) {
|
|
254
|
+
url.searchParams.set('login_hint', params.loginHint);
|
|
255
|
+
}
|
|
256
|
+
return url.toString();
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function base64UrlEncode(arrayBuffer) {
|
|
260
|
+
const bytes = new Uint8Array(arrayBuffer);
|
|
261
|
+
let str = '';
|
|
262
|
+
for (let i = 0; i < bytes.byteLength; i += 1) {
|
|
263
|
+
str += String.fromCharCode(bytes[i]);
|
|
264
|
+
}
|
|
265
|
+
return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
266
|
+
}
|
|
267
|
+
function generateCodeVerifier(length = 64) {
|
|
268
|
+
const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
|
|
269
|
+
const randomValues = new Uint8Array(length);
|
|
270
|
+
crypto.getRandomValues(randomValues);
|
|
271
|
+
let verifier = '';
|
|
272
|
+
for (let i = 0; i < randomValues.length; i += 1) {
|
|
273
|
+
verifier += charset[randomValues[i] % charset.length];
|
|
274
|
+
}
|
|
275
|
+
return verifier;
|
|
276
|
+
}
|
|
277
|
+
async function generateCodeChallenge(verifier) {
|
|
278
|
+
const enc = new TextEncoder();
|
|
279
|
+
const data = enc.encode(verifier);
|
|
280
|
+
const digest = await crypto.subtle.digest('SHA-256', data);
|
|
281
|
+
return base64UrlEncode(digest);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function createMemoryStorage() {
|
|
285
|
+
let mem = {};
|
|
286
|
+
return {
|
|
287
|
+
getItem: (key) => (key in mem ? mem[key] : null),
|
|
288
|
+
setItem: (key, value) => {
|
|
289
|
+
mem[key] = String(value);
|
|
290
|
+
},
|
|
291
|
+
removeItem: (key) => {
|
|
292
|
+
delete mem[key];
|
|
293
|
+
},
|
|
294
|
+
clear: () => {
|
|
295
|
+
mem = {};
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const initialState = {
|
|
301
|
+
isAuthenticated: false,
|
|
302
|
+
tenantKey: '',
|
|
303
|
+
landingPage: '',
|
|
304
|
+
accessToken: '',
|
|
305
|
+
refreshToken: '',
|
|
306
|
+
idToken: '',
|
|
307
|
+
expiresAt: 0,
|
|
308
|
+
error: '',
|
|
309
|
+
};
|
|
310
|
+
class TokenIdpAuthService {
|
|
311
|
+
injectedConfig = inject(TOKEN_IDP_AUTH_CONFIG);
|
|
312
|
+
config = {
|
|
313
|
+
...this.injectedConfig,
|
|
314
|
+
tenantPropagationMode: normalizeTenantPropagationMode(this.injectedConfig.tenantPropagationMode),
|
|
315
|
+
};
|
|
316
|
+
platformId = inject(PLATFORM_ID);
|
|
317
|
+
document = inject(DOCUMENT);
|
|
318
|
+
isBrowser = isPlatformBrowser(this.platformId);
|
|
319
|
+
storage = this.createStorage();
|
|
320
|
+
refreshTimer = null;
|
|
321
|
+
refreshInFlight = false;
|
|
322
|
+
stateSignal = signal(this.buildInitialState(), ...(ngDevMode ? [{ debugName: "stateSignal" }] : /* istanbul ignore next */ []));
|
|
323
|
+
state = this.stateSignal.asReadonly();
|
|
324
|
+
isAuthenticated = computed(() => this.stateSignal().isAuthenticated, ...(ngDevMode ? [{ debugName: "isAuthenticated" }] : /* istanbul ignore next */ []));
|
|
325
|
+
accessToken = computed(() => this.stateSignal().accessToken, ...(ngDevMode ? [{ debugName: "accessToken" }] : /* istanbul ignore next */ []));
|
|
326
|
+
refreshToken = computed(() => this.stateSignal().refreshToken, ...(ngDevMode ? [{ debugName: "refreshToken" }] : /* istanbul ignore next */ []));
|
|
327
|
+
idToken = computed(() => this.stateSignal().idToken, ...(ngDevMode ? [{ debugName: "idToken" }] : /* istanbul ignore next */ []));
|
|
328
|
+
expiresAt = computed(() => this.stateSignal().expiresAt, ...(ngDevMode ? [{ debugName: "expiresAt" }] : /* istanbul ignore next */ []));
|
|
329
|
+
tenantKey = computed(() => this.stateSignal().tenantKey, ...(ngDevMode ? [{ debugName: "tenantKey" }] : /* istanbul ignore next */ []));
|
|
330
|
+
error = computed(() => this.stateSignal().error, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
331
|
+
landingPage = computed(() => this.stateSignal().landingPage, ...(ngDevMode ? [{ debugName: "landingPage" }] : /* istanbul ignore next */ []));
|
|
332
|
+
constructor() {
|
|
333
|
+
this.persistState(this.stateSignal());
|
|
334
|
+
this.scheduleAutoRefresh(this.stateSignal().expiresAt);
|
|
335
|
+
}
|
|
336
|
+
async login(options = {}) {
|
|
337
|
+
if (!this.isBrowser) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (!this.config.authority || !this.config.clientId || !this.config.redirectUri) {
|
|
341
|
+
throw new Error('Missing authority/clientId/redirectUri in TokenIDP auth config.');
|
|
342
|
+
}
|
|
343
|
+
const verifier = generateCodeVerifier();
|
|
344
|
+
const challenge = await generateCodeChallenge(verifier);
|
|
345
|
+
const stateVal = randomState();
|
|
346
|
+
const locationSearch = this.document.defaultView?.location.search || '';
|
|
347
|
+
const authorizeTenantKey = resolveAuthTenantKey(this.config, options, locationSearch);
|
|
348
|
+
const apiTenantKey = resolveApiTenantKey(this.config, options, locationSearch);
|
|
349
|
+
sessionStorage.setItem(this.config.pkceVerifierKey || 'idp_pkce_verifier', verifier);
|
|
350
|
+
sessionStorage.setItem(this.config.oauthStateKey || 'idp_oauth_state', stateVal);
|
|
351
|
+
if (apiTenantKey) {
|
|
352
|
+
sessionStorage.setItem(this.config.tenantKeyStorageKey || 'idp_tenant_key', apiTenantKey);
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
sessionStorage.removeItem(this.config.tenantKeyStorageKey || 'idp_tenant_key');
|
|
356
|
+
}
|
|
357
|
+
const authorizeUrl = buildAuthorizeUrl(this.config, {
|
|
358
|
+
...options,
|
|
359
|
+
codeChallenge: challenge,
|
|
360
|
+
state: stateVal,
|
|
361
|
+
tenantKey: authorizeTenantKey,
|
|
362
|
+
});
|
|
363
|
+
this.document.defaultView?.location.assign(authorizeUrl);
|
|
364
|
+
}
|
|
365
|
+
async logout() {
|
|
366
|
+
const state = this.stateSignal();
|
|
367
|
+
const logoutUrl = buildLogoutUrl(this.config, this.document.defaultView?.location.origin || '');
|
|
368
|
+
this.clearRefreshTimer();
|
|
369
|
+
try {
|
|
370
|
+
await revokeToken(this.config, {
|
|
371
|
+
accessToken: state.accessToken,
|
|
372
|
+
token: state.refreshToken,
|
|
373
|
+
reasonRevoked: 'logout',
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
console.warn('Token revocation during logout failed.', error);
|
|
378
|
+
}
|
|
379
|
+
if (this.isBrowser && logoutUrl) {
|
|
380
|
+
this.document.defaultView?.addEventListener('pagehide', () => this.clearLocalSession(), {
|
|
381
|
+
once: true,
|
|
382
|
+
});
|
|
383
|
+
this.document.defaultView?.location.assign(logoutUrl);
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
this.clearLocalSession();
|
|
387
|
+
}
|
|
388
|
+
async handleCallback(params) {
|
|
389
|
+
if (!this.isBrowser) {
|
|
390
|
+
throw new Error('OAuth callback handling requires a browser.');
|
|
391
|
+
}
|
|
392
|
+
const verifier = sessionStorage.getItem(this.config.pkceVerifierKey || 'idp_pkce_verifier');
|
|
393
|
+
if (!verifier) {
|
|
394
|
+
throw new Error('Missing code verifier (PKCE).');
|
|
395
|
+
}
|
|
396
|
+
const expectedState = sessionStorage.getItem(this.config.oauthStateKey || 'idp_oauth_state');
|
|
397
|
+
if (expectedState && params.state && expectedState !== params.state) {
|
|
398
|
+
throw new Error('Invalid OAuth state. Possible CSRF.');
|
|
399
|
+
}
|
|
400
|
+
const locationSearch = this.document.defaultView?.location.search || '';
|
|
401
|
+
const tenantKey = resolveApiTenantKey(this.config, {}, locationSearch);
|
|
402
|
+
const tokenPayload = await exchangeAuthorizationCode(this.config, {
|
|
403
|
+
grantType: 'authorization_code',
|
|
404
|
+
clientId: this.config.clientId,
|
|
405
|
+
redirectUri: this.config.redirectUri,
|
|
406
|
+
code: params.code,
|
|
407
|
+
codeVerifier: verifier,
|
|
408
|
+
scope: this.config.scope,
|
|
409
|
+
});
|
|
410
|
+
const { accessToken, refreshToken, expiresIn, idToken } = extractToken(tokenPayload);
|
|
411
|
+
if (!accessToken) {
|
|
412
|
+
throw new Error('Token response did not include an access token.');
|
|
413
|
+
}
|
|
414
|
+
const expiresAt = expiresIn ? Date.now() + expiresIn * 1000 : 0;
|
|
415
|
+
const result = {
|
|
416
|
+
tenantKey,
|
|
417
|
+
accessToken,
|
|
418
|
+
refreshToken: refreshToken || '',
|
|
419
|
+
idToken: idToken || '',
|
|
420
|
+
expiresAt,
|
|
421
|
+
};
|
|
422
|
+
this.updateState({
|
|
423
|
+
...result,
|
|
424
|
+
isAuthenticated: true,
|
|
425
|
+
error: '',
|
|
426
|
+
landingPage: this.config.postLoginRedirectUri || '/',
|
|
427
|
+
});
|
|
428
|
+
sessionStorage.removeItem(this.config.pkceVerifierKey || 'idp_pkce_verifier');
|
|
429
|
+
sessionStorage.removeItem(this.config.oauthStateKey || 'idp_oauth_state');
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
async refresh() {
|
|
433
|
+
const state = this.stateSignal();
|
|
434
|
+
if (!state.refreshToken) {
|
|
435
|
+
throw new Error('No refresh token available.');
|
|
436
|
+
}
|
|
437
|
+
const tokenPayload = await refreshWithToken(this.config, {
|
|
438
|
+
grantType: 'refresh_token',
|
|
439
|
+
clientId: this.config.clientId,
|
|
440
|
+
refreshToken: state.refreshToken,
|
|
441
|
+
scope: this.config.scope,
|
|
442
|
+
});
|
|
443
|
+
const { accessToken, refreshToken, expiresIn, idToken } = extractToken(tokenPayload);
|
|
444
|
+
if (!accessToken) {
|
|
445
|
+
throw new Error('Refresh response did not include an access token.');
|
|
446
|
+
}
|
|
447
|
+
const expiresAt = expiresIn ? Date.now() + expiresIn * 1000 : 0;
|
|
448
|
+
const result = {
|
|
449
|
+
accessToken,
|
|
450
|
+
refreshToken: refreshToken || state.refreshToken,
|
|
451
|
+
idToken: idToken || state.idToken,
|
|
452
|
+
expiresAt,
|
|
453
|
+
};
|
|
454
|
+
this.updateState(result);
|
|
455
|
+
return result;
|
|
456
|
+
}
|
|
457
|
+
setError(message) {
|
|
458
|
+
this.updateState({ error: message || 'Unknown error' });
|
|
459
|
+
}
|
|
460
|
+
updateState(partial) {
|
|
461
|
+
this.stateSignal.update((current) => {
|
|
462
|
+
const next = { ...current, ...partial };
|
|
463
|
+
this.persistState(next);
|
|
464
|
+
this.scheduleAutoRefresh(next.expiresAt);
|
|
465
|
+
return next;
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
buildInitialState() {
|
|
469
|
+
const persistedRaw = this.storage.getItem(this.config.storageKey || 'idp_user');
|
|
470
|
+
const persisted = persistedRaw ? safeJsonParse(persistedRaw) : null;
|
|
471
|
+
const locationSearch = this.document.defaultView?.location.search || '';
|
|
472
|
+
const tenantKey = resolveApiTenantKey({
|
|
473
|
+
...this.config,
|
|
474
|
+
tenantKey: persisted?.tenantKey || this.config.tenantKey,
|
|
475
|
+
}, {}, locationSearch);
|
|
476
|
+
if (!persisted) {
|
|
477
|
+
return { ...initialState, tenantKey };
|
|
478
|
+
}
|
|
479
|
+
return {
|
|
480
|
+
...initialState,
|
|
481
|
+
isAuthenticated: !!persisted.isAuthenticated,
|
|
482
|
+
landingPage: persisted.landingPage || '',
|
|
483
|
+
accessToken: persisted.accessToken || '',
|
|
484
|
+
refreshToken: persisted.refreshToken || '',
|
|
485
|
+
idToken: persisted.idToken || '',
|
|
486
|
+
expiresAt: persisted.expiresAt || 0,
|
|
487
|
+
error: persisted.error || '',
|
|
488
|
+
tenantKey,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
persistState(state) {
|
|
492
|
+
this.storage.setItem(this.config.storageKey || 'idp_user', JSON.stringify(state));
|
|
493
|
+
}
|
|
494
|
+
createStorage() {
|
|
495
|
+
if (!this.isBrowser) {
|
|
496
|
+
return createMemoryStorage();
|
|
497
|
+
}
|
|
498
|
+
if (this.config.storage === 'localStorage') {
|
|
499
|
+
return this.document.defaultView?.localStorage || createMemoryStorage();
|
|
500
|
+
}
|
|
501
|
+
if (this.config.storage === 'sessionStorage') {
|
|
502
|
+
return this.document.defaultView?.sessionStorage || createMemoryStorage();
|
|
503
|
+
}
|
|
504
|
+
return createMemoryStorage();
|
|
505
|
+
}
|
|
506
|
+
clearLocalSession() {
|
|
507
|
+
this.storage.removeItem(this.config.storageKey || 'idp_user');
|
|
508
|
+
if (this.isBrowser) {
|
|
509
|
+
sessionStorage.removeItem(this.config.pkceVerifierKey || 'idp_pkce_verifier');
|
|
510
|
+
sessionStorage.removeItem(this.config.oauthStateKey || 'idp_oauth_state');
|
|
511
|
+
sessionStorage.removeItem(this.config.tenantKeyStorageKey || 'idp_tenant_key');
|
|
512
|
+
}
|
|
513
|
+
this.stateSignal.set({ ...initialState });
|
|
514
|
+
}
|
|
515
|
+
scheduleAutoRefresh(nextExpiresAtMs) {
|
|
516
|
+
this.clearRefreshTimer();
|
|
517
|
+
if (!this.config.autoRefresh || !nextExpiresAtMs || !this.isBrowser) {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
const skewMs = (this.config.refreshSkewSeconds || 60) * 1000;
|
|
521
|
+
const delay = Math.max(0, nextExpiresAtMs - Date.now() - skewMs);
|
|
522
|
+
this.refreshTimer = setTimeout(async () => {
|
|
523
|
+
if (this.refreshInFlight) {
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
this.refreshInFlight = true;
|
|
527
|
+
const ok = await this.tryRefreshWithRetry(1, 5000);
|
|
528
|
+
this.refreshInFlight = false;
|
|
529
|
+
if (!ok) {
|
|
530
|
+
await this.logout();
|
|
531
|
+
}
|
|
532
|
+
}, delay);
|
|
533
|
+
}
|
|
534
|
+
clearRefreshTimer() {
|
|
535
|
+
if (this.refreshTimer) {
|
|
536
|
+
clearTimeout(this.refreshTimer);
|
|
537
|
+
this.refreshTimer = null;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
async tryRefreshWithRetry(retries, retryDelayMs) {
|
|
541
|
+
try {
|
|
542
|
+
await this.refresh();
|
|
543
|
+
return true;
|
|
544
|
+
}
|
|
545
|
+
catch {
|
|
546
|
+
if (retries > 0) {
|
|
547
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
548
|
+
return this.tryRefreshWithRetry(retries - 1, retryDelayMs);
|
|
549
|
+
}
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: TokenIdpAuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
554
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: TokenIdpAuthService, providedIn: 'root' });
|
|
555
|
+
}
|
|
556
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: TokenIdpAuthService, decorators: [{
|
|
557
|
+
type: Injectable,
|
|
558
|
+
args: [{ providedIn: 'root' }]
|
|
559
|
+
}], ctorParameters: () => [] });
|
|
560
|
+
function safeJsonParse(raw) {
|
|
561
|
+
try {
|
|
562
|
+
return JSON.parse(raw);
|
|
563
|
+
}
|
|
564
|
+
catch {
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
class IdpAuthCallbackComponent {
|
|
570
|
+
redirectTo = '';
|
|
571
|
+
logoUrl = '';
|
|
572
|
+
logoAlt = 'Application logo';
|
|
573
|
+
fallbackBadge = 'ID';
|
|
574
|
+
error = '';
|
|
575
|
+
auth = inject(TokenIdpAuthService);
|
|
576
|
+
router = inject(Router);
|
|
577
|
+
async ngOnInit() {
|
|
578
|
+
const qs = new URLSearchParams(window.location.search);
|
|
579
|
+
const code = qs.get('code');
|
|
580
|
+
const state = qs.get('state');
|
|
581
|
+
const err = qs.get('error');
|
|
582
|
+
const errDesc = qs.get('error_description');
|
|
583
|
+
if (err) {
|
|
584
|
+
this.setError(errDesc || err || 'Authentication error.');
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
if (!code || !state) {
|
|
588
|
+
this.setError('Missing authorization code or state.');
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
try {
|
|
592
|
+
await this.auth.handleCallback({ code, state });
|
|
593
|
+
await this.router.navigateByUrl(this.redirectTo || this.auth.landingPage() || '/', {
|
|
594
|
+
replaceUrl: true,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
catch (error) {
|
|
598
|
+
this.setError(error instanceof Error ? error.message : 'Login callback failed.');
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
setError(message) {
|
|
602
|
+
this.error = message;
|
|
603
|
+
this.auth.setError(message);
|
|
604
|
+
}
|
|
605
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: IdpAuthCallbackComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
606
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.13", type: IdpAuthCallbackComponent, isStandalone: true, selector: "tokenidp-auth-callback", inputs: { redirectTo: "redirectTo", logoUrl: "logoUrl", logoAlt: "logoAlt", fallbackBadge: "fallbackBadge" }, ngImport: i0, template: `
|
|
607
|
+
<div class="idp-login-page">
|
|
608
|
+
<section class="idp-login-section-container">
|
|
609
|
+
<div class="idp-login-section idp-redirect-card">
|
|
610
|
+
<div class="idp-content">
|
|
611
|
+
<ng-container *ngIf="logoUrl; else fallbackLogo">
|
|
612
|
+
<div class="idp-logo-image-wrap">
|
|
613
|
+
<img [src]="logoUrl" [alt]="logoAlt" width="250" />
|
|
614
|
+
</div>
|
|
615
|
+
</ng-container>
|
|
616
|
+
<ng-template #fallbackLogo>
|
|
617
|
+
<div class="idp-logo">{{ fallbackBadge }}</div>
|
|
618
|
+
</ng-template>
|
|
619
|
+
<h1>Completing sign-in</h1>
|
|
620
|
+
<p>{{ error || 'Please wait while we finish authentication.' }}</p>
|
|
621
|
+
</div>
|
|
622
|
+
</div>
|
|
623
|
+
</section>
|
|
624
|
+
</div>
|
|
625
|
+
`, isInline: true, styles: [".idp-login-page{min-height:100vh;display:grid;place-items:center;background:#f5f7fb;color:#151923;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}.idp-login-section-container{width:min(440px,calc(100vw - 32px))}.idp-login-section{background:#fff;border:1px solid #e4e8f0;border-radius:8px;box-shadow:0 18px 45px #0f172a14}.idp-content{padding:32px;text-align:center}.idp-logo,.idp-logo-image-wrap{margin:0 auto 16px}.idp-logo{width:56px;height:56px;display:grid;place-items:center;border-radius:8px;background:#111827;color:#fff;font-weight:700}h1{margin:0 0 8px;font-size:24px;line-height:1.25}p{margin:0;color:#667085;line-height:1.5}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
626
|
+
}
|
|
627
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: IdpAuthCallbackComponent, decorators: [{
|
|
628
|
+
type: Component,
|
|
629
|
+
args: [{ selector: 'tokenidp-auth-callback', standalone: true, imports: [CommonModule], template: `
|
|
630
|
+
<div class="idp-login-page">
|
|
631
|
+
<section class="idp-login-section-container">
|
|
632
|
+
<div class="idp-login-section idp-redirect-card">
|
|
633
|
+
<div class="idp-content">
|
|
634
|
+
<ng-container *ngIf="logoUrl; else fallbackLogo">
|
|
635
|
+
<div class="idp-logo-image-wrap">
|
|
636
|
+
<img [src]="logoUrl" [alt]="logoAlt" width="250" />
|
|
637
|
+
</div>
|
|
638
|
+
</ng-container>
|
|
639
|
+
<ng-template #fallbackLogo>
|
|
640
|
+
<div class="idp-logo">{{ fallbackBadge }}</div>
|
|
641
|
+
</ng-template>
|
|
642
|
+
<h1>Completing sign-in</h1>
|
|
643
|
+
<p>{{ error || 'Please wait while we finish authentication.' }}</p>
|
|
644
|
+
</div>
|
|
645
|
+
</div>
|
|
646
|
+
</section>
|
|
647
|
+
</div>
|
|
648
|
+
`, styles: [".idp-login-page{min-height:100vh;display:grid;place-items:center;background:#f5f7fb;color:#151923;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}.idp-login-section-container{width:min(440px,calc(100vw - 32px))}.idp-login-section{background:#fff;border:1px solid #e4e8f0;border-radius:8px;box-shadow:0 18px 45px #0f172a14}.idp-content{padding:32px;text-align:center}.idp-logo,.idp-logo-image-wrap{margin:0 auto 16px}.idp-logo{width:56px;height:56px;display:grid;place-items:center;border-radius:8px;background:#111827;color:#fff;font-weight:700}h1{margin:0 0 8px;font-size:24px;line-height:1.25}p{margin:0;color:#667085;line-height:1.5}\n"] }]
|
|
649
|
+
}], propDecorators: { redirectTo: [{
|
|
650
|
+
type: Input
|
|
651
|
+
}], logoUrl: [{
|
|
652
|
+
type: Input
|
|
653
|
+
}], logoAlt: [{
|
|
654
|
+
type: Input
|
|
655
|
+
}], fallbackBadge: [{
|
|
656
|
+
type: Input
|
|
657
|
+
}] } });
|
|
658
|
+
|
|
659
|
+
class IdpLoginComponent {
|
|
660
|
+
logoUrl = '';
|
|
661
|
+
logoAlt = 'Application logo';
|
|
662
|
+
title = 'Redirecting to sign-in...';
|
|
663
|
+
subtitle = 'Please wait while we securely connect to Identity.';
|
|
664
|
+
signedOutTitle = 'You have been signed out';
|
|
665
|
+
signedOutSubtitle = 'Start a new session when you are ready.';
|
|
666
|
+
signInAgainLabel = 'Sign in again';
|
|
667
|
+
fallbackBadge = 'ID';
|
|
668
|
+
loginOptions;
|
|
669
|
+
isLoggedOut = false;
|
|
670
|
+
auth = inject(TokenIdpAuthService);
|
|
671
|
+
ngOnInit() {
|
|
672
|
+
this.isLoggedOut = new URLSearchParams(window.location.search).get('logged_out') === '1';
|
|
673
|
+
if (!this.isLoggedOut) {
|
|
674
|
+
void this.auth.login(this.loginOptions);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
signInAgain() {
|
|
678
|
+
void this.auth.login(this.loginOptions);
|
|
679
|
+
}
|
|
680
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: IdpLoginComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
681
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.13", type: IdpLoginComponent, isStandalone: true, selector: "tokenidp-login", inputs: { logoUrl: "logoUrl", logoAlt: "logoAlt", title: "title", subtitle: "subtitle", signedOutTitle: "signedOutTitle", signedOutSubtitle: "signedOutSubtitle", signInAgainLabel: "signInAgainLabel", fallbackBadge: "fallbackBadge", loginOptions: "loginOptions" }, ngImport: i0, template: `
|
|
682
|
+
<div class="idp-login-page">
|
|
683
|
+
<section class="idp-login-section-container">
|
|
684
|
+
<div class="idp-login-section idp-redirect-card">
|
|
685
|
+
<div class="idp-content">
|
|
686
|
+
<ng-container *ngIf="logoUrl; else fallbackLogo">
|
|
687
|
+
<div class="idp-logo-image-wrap">
|
|
688
|
+
<img [src]="logoUrl" [alt]="logoAlt" width="250" />
|
|
689
|
+
</div>
|
|
690
|
+
</ng-container>
|
|
691
|
+
<ng-template #fallbackLogo>
|
|
692
|
+
<div class="idp-logo">{{ fallbackBadge }}</div>
|
|
693
|
+
</ng-template>
|
|
694
|
+
|
|
695
|
+
<ng-container *ngIf="isLoggedOut; else redirecting">
|
|
696
|
+
<h1>{{ signedOutTitle }}</h1>
|
|
697
|
+
<p>{{ signedOutSubtitle }}</p>
|
|
698
|
+
<button type="button" class="idp-button" (click)="signInAgain()">
|
|
699
|
+
{{ signInAgainLabel }}
|
|
700
|
+
</button>
|
|
701
|
+
</ng-container>
|
|
702
|
+
|
|
703
|
+
<ng-template #redirecting>
|
|
704
|
+
<h1>{{ title }}</h1>
|
|
705
|
+
<p>{{ subtitle }}</p>
|
|
706
|
+
<div class="idp-spinner" aria-hidden="true"></div>
|
|
707
|
+
</ng-template>
|
|
708
|
+
</div>
|
|
709
|
+
</div>
|
|
710
|
+
</section>
|
|
711
|
+
</div>
|
|
712
|
+
`, isInline: true, styles: [".idp-login-page{min-height:100vh;display:grid;place-items:center;background:#f5f7fb;color:#151923;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}.idp-login-section-container{width:min(440px,calc(100vw - 32px))}.idp-login-section{background:#fff;border:1px solid #e4e8f0;border-radius:8px;box-shadow:0 18px 45px #0f172a14}.idp-content{padding:32px;text-align:center}.idp-logo,.idp-logo-image-wrap{margin:0 auto 16px}.idp-logo{width:56px;height:56px;display:grid;place-items:center;border-radius:8px;background:#111827;color:#fff;font-weight:700}h1{margin:0 0 8px;font-size:24px;line-height:1.25}p{margin:0;color:#667085;line-height:1.5}.idp-button{margin-top:20px;min-height:40px;border:0;border-radius:6px;padding:0 16px;background:#0f62fe;color:#fff;font-weight:600;cursor:pointer}.idp-spinner{width:28px;height:28px;border:3px solid #d0d5dd;border-top-color:#0f62fe;border-radius:50%;margin:20px auto 0;animation:idp-spin .8s linear infinite}@keyframes idp-spin{to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
713
|
+
}
|
|
714
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.13", ngImport: i0, type: IdpLoginComponent, decorators: [{
|
|
715
|
+
type: Component,
|
|
716
|
+
args: [{ selector: 'tokenidp-login', standalone: true, imports: [CommonModule], template: `
|
|
717
|
+
<div class="idp-login-page">
|
|
718
|
+
<section class="idp-login-section-container">
|
|
719
|
+
<div class="idp-login-section idp-redirect-card">
|
|
720
|
+
<div class="idp-content">
|
|
721
|
+
<ng-container *ngIf="logoUrl; else fallbackLogo">
|
|
722
|
+
<div class="idp-logo-image-wrap">
|
|
723
|
+
<img [src]="logoUrl" [alt]="logoAlt" width="250" />
|
|
724
|
+
</div>
|
|
725
|
+
</ng-container>
|
|
726
|
+
<ng-template #fallbackLogo>
|
|
727
|
+
<div class="idp-logo">{{ fallbackBadge }}</div>
|
|
728
|
+
</ng-template>
|
|
729
|
+
|
|
730
|
+
<ng-container *ngIf="isLoggedOut; else redirecting">
|
|
731
|
+
<h1>{{ signedOutTitle }}</h1>
|
|
732
|
+
<p>{{ signedOutSubtitle }}</p>
|
|
733
|
+
<button type="button" class="idp-button" (click)="signInAgain()">
|
|
734
|
+
{{ signInAgainLabel }}
|
|
735
|
+
</button>
|
|
736
|
+
</ng-container>
|
|
737
|
+
|
|
738
|
+
<ng-template #redirecting>
|
|
739
|
+
<h1>{{ title }}</h1>
|
|
740
|
+
<p>{{ subtitle }}</p>
|
|
741
|
+
<div class="idp-spinner" aria-hidden="true"></div>
|
|
742
|
+
</ng-template>
|
|
743
|
+
</div>
|
|
744
|
+
</div>
|
|
745
|
+
</section>
|
|
746
|
+
</div>
|
|
747
|
+
`, styles: [".idp-login-page{min-height:100vh;display:grid;place-items:center;background:#f5f7fb;color:#151923;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}.idp-login-section-container{width:min(440px,calc(100vw - 32px))}.idp-login-section{background:#fff;border:1px solid #e4e8f0;border-radius:8px;box-shadow:0 18px 45px #0f172a14}.idp-content{padding:32px;text-align:center}.idp-logo,.idp-logo-image-wrap{margin:0 auto 16px}.idp-logo{width:56px;height:56px;display:grid;place-items:center;border-radius:8px;background:#111827;color:#fff;font-weight:700}h1{margin:0 0 8px;font-size:24px;line-height:1.25}p{margin:0;color:#667085;line-height:1.5}.idp-button{margin-top:20px;min-height:40px;border:0;border-radius:6px;padding:0 16px;background:#0f62fe;color:#fff;font-weight:600;cursor:pointer}.idp-spinner{width:28px;height:28px;border:3px solid #d0d5dd;border-top-color:#0f62fe;border-radius:50%;margin:20px auto 0;animation:idp-spin .8s linear infinite}@keyframes idp-spin{to{transform:rotate(360deg)}}\n"] }]
|
|
748
|
+
}], propDecorators: { logoUrl: [{
|
|
749
|
+
type: Input
|
|
750
|
+
}], logoAlt: [{
|
|
751
|
+
type: Input
|
|
752
|
+
}], title: [{
|
|
753
|
+
type: Input
|
|
754
|
+
}], subtitle: [{
|
|
755
|
+
type: Input
|
|
756
|
+
}], signedOutTitle: [{
|
|
757
|
+
type: Input
|
|
758
|
+
}], signedOutSubtitle: [{
|
|
759
|
+
type: Input
|
|
760
|
+
}], signInAgainLabel: [{
|
|
761
|
+
type: Input
|
|
762
|
+
}], fallbackBadge: [{
|
|
763
|
+
type: Input
|
|
764
|
+
}], loginOptions: [{
|
|
765
|
+
type: Input
|
|
766
|
+
}] } });
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Generated bundle index. Do not edit.
|
|
770
|
+
*/
|
|
771
|
+
|
|
772
|
+
export { IdpAuthCallbackComponent, IdpLoginComponent, TOKEN_IDP_AUTH_CONFIG, TokenIdpAuthService, defaultAuthConfig, provideTokenIdpAuth };
|
|
773
|
+
//# sourceMappingURL=tokenidp-angular.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenidp-angular.mjs","sources":["../../src/lib/auth.config.ts","../../src/lib/tenant.ts","../../src/lib/auth-api.ts","../../src/lib/oauth.ts","../../src/lib/pkce.ts","../../src/lib/storage.ts","../../src/lib/auth.service.ts","../../src/lib/auth-callback.component.ts","../../src/lib/login.component.ts","../../src/tokenidp-angular.ts"],"sourcesContent":["import { EnvironmentProviders, InjectionToken, makeEnvironmentProviders } from '@angular/core';\nimport { TokenIdpAuthConfig } from './models';\n\nexport const defaultAuthConfig: TokenIdpAuthConfig = {\n authority: '',\n clientId: '',\n tenantKey: '',\n tenantPropagationMode: 'all',\n tenantQueryParameter: 'tenant',\n tenantHeaderName: 'X-Tenant-Key',\n tenantKeyStorageKey: 'idp_tenant_key',\n redirectUri: '',\n postLoginRedirectUri: '/',\n postLogoutRedirectUri: '/login',\n scope: 'openid profile offline_access',\n audience: '',\n authorizePath: '/authorize',\n tokenPath: '/token',\n revokePath: '/revoke',\n logoutPath: '/logout',\n storage: 'sessionStorage',\n storageKey: 'idp_user',\n pkceVerifierKey: 'idp_pkce_verifier',\n oauthStateKey: 'idp_oauth_state',\n autoRefresh: true,\n refreshSkewSeconds: 180,\n};\n\nexport const TOKEN_IDP_AUTH_CONFIG = new InjectionToken<TokenIdpAuthConfig>(\n 'TOKEN_IDP_AUTH_CONFIG',\n {\n factory: () => defaultAuthConfig,\n },\n);\n\nexport function provideTokenIdpAuth(config: Partial<TokenIdpAuthConfig>): EnvironmentProviders {\n return makeEnvironmentProviders([\n {\n provide: TOKEN_IDP_AUTH_CONFIG,\n useValue: {\n ...defaultAuthConfig,\n ...config,\n },\n },\n ]);\n}\n","import { TokenIdpAuthConfig, TokenIdpLoginOptions, TokenIdpTenantPropagationMode } from './models';\n\nexport function normalizeTenantPropagationMode(value: unknown): TokenIdpTenantPropagationMode {\n const normalized = String(value || 'all').trim().toLowerCase();\n\n if (normalized === 'none' || normalized === 'api') {\n return normalized;\n }\n\n return 'all';\n}\n\nexport function getAuthTenantKey(config: TokenIdpAuthConfig): string {\n return normalizeTenantPropagationMode(config.tenantPropagationMode) === 'all'\n ? String(config.tenantKey || '').trim()\n : '';\n}\n\nexport function getApiTenantKey(config: TokenIdpAuthConfig): string {\n const mode = normalizeTenantPropagationMode(config.tenantPropagationMode);\n\n return mode === 'all' || mode === 'api' ? String(config.tenantKey || '').trim() : '';\n}\n\nexport function resolveRawTenantKey(\n config: TokenIdpAuthConfig,\n overrides: TokenIdpLoginOptions = {},\n locationSearch = '',\n): string {\n const explicitTenantKey = String(overrides.tenantKey || config.tenantKey || '').trim();\n if (explicitTenantKey) {\n return explicitTenantKey;\n }\n\n const tenantFromQuery = new URLSearchParams(locationSearch).get(\n config.tenantQueryParameter || 'tenant',\n );\n if (tenantFromQuery) {\n return tenantFromQuery.trim();\n }\n\n const tenantFromStorage = globalThis.sessionStorage?.getItem(\n config.tenantKeyStorageKey || 'idp_tenant_key',\n );\n if (tenantFromStorage) {\n return tenantFromStorage.trim();\n }\n\n return '';\n}\n\nexport function resolveAuthTenantKey(\n config: TokenIdpAuthConfig,\n overrides: TokenIdpLoginOptions = {},\n locationSearch = '',\n): string {\n return normalizeTenantPropagationMode(config.tenantPropagationMode) === 'all'\n ? resolveRawTenantKey(config, overrides, locationSearch)\n : '';\n}\n\nexport function resolveApiTenantKey(\n config: TokenIdpAuthConfig,\n overrides: TokenIdpLoginOptions = {},\n locationSearch = '',\n): string {\n const mode = normalizeTenantPropagationMode(config.tenantPropagationMode);\n\n return mode === 'all' || mode === 'api' ? resolveRawTenantKey(config, overrides, locationSearch) : '';\n}\n","import { TokenIdpAuthConfig, TokenIdpTokenParts } from './models';\nimport { getApiTenantKey, getAuthTenantKey } from './tenant';\n\ninterface TokenEndpointPayload {\n grantType: 'authorization_code' | 'refresh_token';\n clientId: string;\n redirectUri?: string;\n code?: string;\n codeVerifier?: string;\n refreshToken?: string;\n scope?: string;\n}\n\nasync function httpPostJson(url: string, body: unknown): Promise<unknown> {\n const res = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n\n const data = await readResponseBody(res);\n\n if (!res.ok) {\n throw buildHttpError(data, res.status);\n }\n\n return data;\n}\n\nasync function readResponseBody(res: Response): Promise<unknown> {\n const text = await res.text();\n if (!text) {\n return null;\n }\n\n try {\n return JSON.parse(text) as unknown;\n } catch {\n return text;\n }\n}\n\nfunction buildHttpError(data: unknown, status: number): Error {\n const err = new Error(getErrorMessage(data, status)) as Error & {\n status?: number;\n data?: unknown;\n };\n err.status = status;\n err.data = data;\n return err;\n}\n\nfunction getErrorMessage(data: unknown, status: number): string {\n if (!data || typeof data !== 'object') {\n return `HTTP ${status}`;\n }\n\n const record = data as Record<string, unknown>;\n const value = record['value'] as Record<string, unknown> | undefined;\n const directError = record['error'];\n const wrappedError =\n directError && typeof directError === 'object'\n ? (directError as Record<string, unknown>)\n : value?.['error'] && typeof value['error'] === 'object'\n ? (value['error'] as Record<string, unknown>)\n : null;\n\n return (\n stringValue(record['error_description']) ||\n stringValue(wrappedError?.['error']) ||\n stringValue(wrappedError?.['Error']) ||\n stringValue(wrappedError?.['message']) ||\n stringValue(wrappedError?.['Message']) ||\n stringValue(directError) ||\n stringValue(record['message']) ||\n `HTTP ${status}`\n );\n}\n\nfunction stringValue(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n\nfunction withTenant(url: string, config: TokenIdpAuthConfig, target: 'api' | 'auth' = 'api'): string {\n const tenantKey = target === 'auth' ? getAuthTenantKey(config) : getApiTenantKey(config);\n if (!tenantKey) {\n return url;\n }\n\n const tenantUrl = new URL(url);\n tenantUrl.searchParams.set(config.tenantQueryParameter || 'tenant', tenantKey);\n return tenantUrl.toString();\n}\n\nexport function extractToken(tokenPayload: unknown): TokenIdpTokenParts {\n if (!tokenPayload || typeof tokenPayload !== 'object') {\n return { accessToken: '', refreshToken: '', expiresIn: 0, idToken: '' };\n }\n\n const record = tokenPayload as Record<string, unknown>;\n const value = ((record['value'] as Record<string, unknown> | undefined) || record) as Record<\n string,\n unknown\n >;\n\n const accessToken = stringValue(value['accessToken']) || stringValue(value['access_token']);\n const refreshToken = stringValue(value['refreshToken']) || stringValue(value['refresh_token']);\n const expiresIn = Number(value['expiresIn'] || value['expires_in'] || 0) || 0;\n const idToken =\n stringValue(value['idToken']) ||\n stringValue(value['id_token']) ||\n stringValue(record['idToken']) ||\n stringValue(record['id_token']);\n\n return { accessToken, refreshToken, expiresIn, idToken };\n}\n\nexport async function exchangeAuthorizationCode(\n config: TokenIdpAuthConfig,\n payload: TokenEndpointPayload,\n): Promise<unknown> {\n const url = withTenant(config.authority + (config.tokenPath || '/token'), config, 'auth');\n return httpPostJson(url, payload);\n}\n\nexport async function refreshWithToken(\n config: TokenIdpAuthConfig,\n payload: TokenEndpointPayload,\n): Promise<unknown> {\n const url = withTenant(config.authority + (config.tokenPath || '/token'), config, 'auth');\n return httpPostJson(url, payload);\n}\n\nexport async function revokeToken(\n config: TokenIdpAuthConfig,\n params: { accessToken: string; token: string; reasonRevoked?: string },\n): Promise<unknown> {\n if (!config.authority || !params.accessToken || !params.token) {\n return null;\n }\n\n const url = withTenant(new URL(config.revokePath || '/revoke', config.authority).toString(), config, 'auth');\n const res = await fetch(url, {\n method: 'DELETE',\n headers: {\n Authorization: `Bearer ${params.accessToken}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n token: params.token,\n reasonRevoked: params.reasonRevoked || 'logout',\n }),\n });\n\n const data = await readResponseBody(res);\n\n if (!res.ok) {\n throw buildHttpError(data, res.status);\n }\n\n return data;\n}\n\nexport function buildLogoutUrl(config: TokenIdpAuthConfig, origin = ''): string {\n if (!config.authority) {\n return '';\n }\n\n const url = new URL(\n withTenant(new URL(config.logoutPath || '/logout', config.authority).toString(), config, 'auth'),\n );\n\n if (config.clientId) {\n url.searchParams.set('client_id', config.clientId);\n }\n\n const postLogoutRedirectUri = resolvePostLogoutRedirectUri(config, origin);\n if (postLogoutRedirectUri) {\n url.searchParams.set('post_logout_redirect_uri', postLogoutRedirectUri);\n }\n\n return url.toString();\n}\n\nfunction resolvePostLogoutRedirectUri(config: TokenIdpAuthConfig, origin: string): string {\n const candidate = config.postLogoutRedirectUri;\n if (!candidate) {\n return '';\n }\n\n if (origin) {\n return new URL(candidate, origin).toString();\n }\n\n if (config.redirectUri) {\n return new URL(candidate, config.redirectUri).toString();\n }\n\n return String(candidate);\n}\n","import { TokenIdpAuthConfig, TokenIdpLoginOptions } from './models';\nimport { getAuthTenantKey, normalizeTenantPropagationMode } from './tenant';\n\nexport function randomState(length = 32): string {\n const bytes = new Uint8Array(length);\n crypto.getRandomValues(bytes);\n return Array.from(bytes)\n .map((byte) => byte.toString(16).padStart(2, '0'))\n .join('');\n}\n\nexport function buildAuthorizeUrl(\n config: TokenIdpAuthConfig,\n params: TokenIdpLoginOptions & { codeChallenge: string; state: string },\n): string {\n const url = new URL(config.authority + (config.authorizePath || '/authorize'));\n const tenantKey = getAuthTenantKey({\n ...config,\n tenantPropagationMode: normalizeTenantPropagationMode(config.tenantPropagationMode),\n tenantKey: params.tenantKey || config.tenantKey,\n });\n\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('client_id', config.clientId);\n url.searchParams.set('redirect_uri', config.redirectUri);\n url.searchParams.set('scope', config.scope || 'openid profile offline_access');\n url.searchParams.set('code_challenge', params.codeChallenge);\n url.searchParams.set('code_challenge_method', 'S256');\n url.searchParams.set('state', params.state);\n\n if (params.audience || config.audience) {\n url.searchParams.set('audience', params.audience || config.audience || '');\n }\n\n if (tenantKey) {\n url.searchParams.set(config.tenantQueryParameter || 'tenant', tenantKey);\n }\n\n if (params.prompt) {\n url.searchParams.set('prompt', params.prompt);\n }\n\n if (params.loginHint) {\n url.searchParams.set('login_hint', params.loginHint);\n }\n\n return url.toString();\n}\n","function base64UrlEncode(arrayBuffer: ArrayBuffer): string {\n const bytes = new Uint8Array(arrayBuffer);\n let str = '';\n\n for (let i = 0; i < bytes.byteLength; i += 1) {\n str += String.fromCharCode(bytes[i]);\n }\n\n return btoa(str).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nexport function generateCodeVerifier(length = 64): string {\n const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n const randomValues = new Uint8Array(length);\n crypto.getRandomValues(randomValues);\n\n let verifier = '';\n for (let i = 0; i < randomValues.length; i += 1) {\n verifier += charset[randomValues[i] % charset.length];\n }\n\n return verifier;\n}\n\nexport async function generateCodeChallenge(verifier: string): Promise<string> {\n const enc = new TextEncoder();\n const data = enc.encode(verifier);\n const digest = await crypto.subtle.digest('SHA-256', data);\n return base64UrlEncode(digest);\n}\n","export interface TokenIdpStorage {\n getItem(key: string): string | null;\n setItem(key: string, value: string): void;\n removeItem(key: string): void;\n clear(): void;\n}\n\nexport function createMemoryStorage(): TokenIdpStorage {\n let mem: Record<string, string> = {};\n\n return {\n getItem: (key) => (key in mem ? mem[key] : null),\n setItem: (key, value) => {\n mem[key] = String(value);\n },\n removeItem: (key) => {\n delete mem[key];\n },\n clear: () => {\n mem = {};\n },\n };\n}\n","import { DOCUMENT, isPlatformBrowser } from '@angular/common';\nimport { Injectable, PLATFORM_ID, computed, inject, signal } from '@angular/core';\nimport { TOKEN_IDP_AUTH_CONFIG } from './auth.config';\nimport { buildLogoutUrl, exchangeAuthorizationCode, extractToken, refreshWithToken, revokeToken } from './auth-api';\nimport {\n TokenIdpAuthState,\n TokenIdpCallbackParams,\n TokenIdpLoginOptions,\n TokenIdpTokenResult,\n} from './models';\nimport { buildAuthorizeUrl, randomState } from './oauth';\nimport { generateCodeChallenge, generateCodeVerifier } from './pkce';\nimport { createMemoryStorage, TokenIdpStorage } from './storage';\nimport { normalizeTenantPropagationMode, resolveApiTenantKey, resolveAuthTenantKey } from './tenant';\n\nconst initialState: TokenIdpAuthState = {\n isAuthenticated: false,\n tenantKey: '',\n landingPage: '',\n accessToken: '',\n refreshToken: '',\n idToken: '',\n expiresAt: 0,\n error: '',\n};\n\n@Injectable({ providedIn: 'root' })\nexport class TokenIdpAuthService {\n private readonly injectedConfig = inject(TOKEN_IDP_AUTH_CONFIG);\n private readonly config = {\n ...this.injectedConfig,\n tenantPropagationMode: normalizeTenantPropagationMode(this.injectedConfig.tenantPropagationMode),\n };\n private readonly platformId = inject(PLATFORM_ID);\n private readonly document = inject(DOCUMENT);\n private readonly isBrowser = isPlatformBrowser(this.platformId);\n private readonly storage = this.createStorage();\n private refreshTimer: ReturnType<typeof setTimeout> | null = null;\n private refreshInFlight = false;\n private readonly stateSignal = signal<TokenIdpAuthState>(this.buildInitialState());\n\n readonly state = this.stateSignal.asReadonly();\n readonly isAuthenticated = computed(() => this.stateSignal().isAuthenticated);\n readonly accessToken = computed(() => this.stateSignal().accessToken);\n readonly refreshToken = computed(() => this.stateSignal().refreshToken);\n readonly idToken = computed(() => this.stateSignal().idToken);\n readonly expiresAt = computed(() => this.stateSignal().expiresAt);\n readonly tenantKey = computed(() => this.stateSignal().tenantKey);\n readonly error = computed(() => this.stateSignal().error);\n readonly landingPage = computed(() => this.stateSignal().landingPage);\n\n constructor() {\n this.persistState(this.stateSignal());\n this.scheduleAutoRefresh(this.stateSignal().expiresAt);\n }\n\n async login(options: TokenIdpLoginOptions = {}): Promise<void> {\n if (!this.isBrowser) {\n return;\n }\n\n if (!this.config.authority || !this.config.clientId || !this.config.redirectUri) {\n throw new Error('Missing authority/clientId/redirectUri in TokenIDP auth config.');\n }\n\n const verifier = generateCodeVerifier();\n const challenge = await generateCodeChallenge(verifier);\n const stateVal = randomState();\n const locationSearch = this.document.defaultView?.location.search || '';\n const authorizeTenantKey = resolveAuthTenantKey(this.config, options, locationSearch);\n const apiTenantKey = resolveApiTenantKey(this.config, options, locationSearch);\n\n sessionStorage.setItem(this.config.pkceVerifierKey || 'idp_pkce_verifier', verifier);\n sessionStorage.setItem(this.config.oauthStateKey || 'idp_oauth_state', stateVal);\n\n if (apiTenantKey) {\n sessionStorage.setItem(this.config.tenantKeyStorageKey || 'idp_tenant_key', apiTenantKey);\n } else {\n sessionStorage.removeItem(this.config.tenantKeyStorageKey || 'idp_tenant_key');\n }\n\n const authorizeUrl = buildAuthorizeUrl(this.config, {\n ...options,\n codeChallenge: challenge,\n state: stateVal,\n tenantKey: authorizeTenantKey,\n });\n\n this.document.defaultView?.location.assign(authorizeUrl);\n }\n\n async logout(): Promise<void> {\n const state = this.stateSignal();\n const logoutUrl = buildLogoutUrl(this.config, this.document.defaultView?.location.origin || '');\n this.clearRefreshTimer();\n\n try {\n await revokeToken(this.config, {\n accessToken: state.accessToken,\n token: state.refreshToken,\n reasonRevoked: 'logout',\n });\n } catch (error) {\n console.warn('Token revocation during logout failed.', error);\n }\n\n if (this.isBrowser && logoutUrl) {\n this.document.defaultView?.addEventListener('pagehide', () => this.clearLocalSession(), {\n once: true,\n });\n this.document.defaultView?.location.assign(logoutUrl);\n return;\n }\n\n this.clearLocalSession();\n }\n\n async handleCallback(params: TokenIdpCallbackParams): Promise<TokenIdpTokenResult> {\n if (!this.isBrowser) {\n throw new Error('OAuth callback handling requires a browser.');\n }\n\n const verifier = sessionStorage.getItem(this.config.pkceVerifierKey || 'idp_pkce_verifier');\n if (!verifier) {\n throw new Error('Missing code verifier (PKCE).');\n }\n\n const expectedState = sessionStorage.getItem(this.config.oauthStateKey || 'idp_oauth_state');\n if (expectedState && params.state && expectedState !== params.state) {\n throw new Error('Invalid OAuth state. Possible CSRF.');\n }\n\n const locationSearch = this.document.defaultView?.location.search || '';\n const tenantKey = resolveApiTenantKey(this.config, {}, locationSearch);\n const tokenPayload = await exchangeAuthorizationCode(this.config, {\n grantType: 'authorization_code',\n clientId: this.config.clientId,\n redirectUri: this.config.redirectUri,\n code: params.code,\n codeVerifier: verifier,\n scope: this.config.scope,\n });\n\n const { accessToken, refreshToken, expiresIn, idToken } = extractToken(tokenPayload);\n if (!accessToken) {\n throw new Error('Token response did not include an access token.');\n }\n\n const expiresAt = expiresIn ? Date.now() + expiresIn * 1000 : 0;\n const result = {\n tenantKey,\n accessToken,\n refreshToken: refreshToken || '',\n idToken: idToken || '',\n expiresAt,\n };\n\n this.updateState({\n ...result,\n isAuthenticated: true,\n error: '',\n landingPage: this.config.postLoginRedirectUri || '/',\n });\n\n sessionStorage.removeItem(this.config.pkceVerifierKey || 'idp_pkce_verifier');\n sessionStorage.removeItem(this.config.oauthStateKey || 'idp_oauth_state');\n\n return result;\n }\n\n async refresh(): Promise<Omit<TokenIdpTokenResult, 'tenantKey'>> {\n const state = this.stateSignal();\n if (!state.refreshToken) {\n throw new Error('No refresh token available.');\n }\n\n const tokenPayload = await refreshWithToken(this.config, {\n grantType: 'refresh_token',\n clientId: this.config.clientId,\n refreshToken: state.refreshToken,\n scope: this.config.scope,\n });\n\n const { accessToken, refreshToken, expiresIn, idToken } = extractToken(tokenPayload);\n if (!accessToken) {\n throw new Error('Refresh response did not include an access token.');\n }\n\n const expiresAt = expiresIn ? Date.now() + expiresIn * 1000 : 0;\n const result = {\n accessToken,\n refreshToken: refreshToken || state.refreshToken,\n idToken: idToken || state.idToken,\n expiresAt,\n };\n\n this.updateState(result);\n return result;\n }\n\n setError(message: string): void {\n this.updateState({ error: message || 'Unknown error' });\n }\n\n private updateState(partial: Partial<TokenIdpAuthState>): void {\n this.stateSignal.update((current) => {\n const next = { ...current, ...partial };\n this.persistState(next);\n this.scheduleAutoRefresh(next.expiresAt);\n return next;\n });\n }\n\n private buildInitialState(): TokenIdpAuthState {\n const persistedRaw = this.storage.getItem(this.config.storageKey || 'idp_user');\n const persisted = persistedRaw ? safeJsonParse(persistedRaw) : null;\n const locationSearch = this.document.defaultView?.location.search || '';\n const tenantKey = resolveApiTenantKey(\n {\n ...this.config,\n tenantKey: persisted?.tenantKey || this.config.tenantKey,\n },\n {},\n locationSearch,\n );\n\n if (!persisted) {\n return { ...initialState, tenantKey };\n }\n\n return {\n ...initialState,\n isAuthenticated: !!persisted.isAuthenticated,\n landingPage: persisted.landingPage || '',\n accessToken: persisted.accessToken || '',\n refreshToken: persisted.refreshToken || '',\n idToken: persisted.idToken || '',\n expiresAt: persisted.expiresAt || 0,\n error: persisted.error || '',\n tenantKey,\n };\n }\n\n private persistState(state: TokenIdpAuthState): void {\n this.storage.setItem(this.config.storageKey || 'idp_user', JSON.stringify(state));\n }\n\n private createStorage(): TokenIdpStorage {\n if (!this.isBrowser) {\n return createMemoryStorage();\n }\n\n if (this.config.storage === 'localStorage') {\n return this.document.defaultView?.localStorage || createMemoryStorage();\n }\n\n if (this.config.storage === 'sessionStorage') {\n return this.document.defaultView?.sessionStorage || createMemoryStorage();\n }\n\n return createMemoryStorage();\n }\n\n private clearLocalSession(): void {\n this.storage.removeItem(this.config.storageKey || 'idp_user');\n\n if (this.isBrowser) {\n sessionStorage.removeItem(this.config.pkceVerifierKey || 'idp_pkce_verifier');\n sessionStorage.removeItem(this.config.oauthStateKey || 'idp_oauth_state');\n sessionStorage.removeItem(this.config.tenantKeyStorageKey || 'idp_tenant_key');\n }\n\n this.stateSignal.set({ ...initialState });\n }\n\n private scheduleAutoRefresh(nextExpiresAtMs: number): void {\n this.clearRefreshTimer();\n if (!this.config.autoRefresh || !nextExpiresAtMs || !this.isBrowser) {\n return;\n }\n\n const skewMs = (this.config.refreshSkewSeconds || 60) * 1000;\n const delay = Math.max(0, nextExpiresAtMs - Date.now() - skewMs);\n\n this.refreshTimer = setTimeout(async () => {\n if (this.refreshInFlight) {\n return;\n }\n\n this.refreshInFlight = true;\n const ok = await this.tryRefreshWithRetry(1, 5000);\n this.refreshInFlight = false;\n\n if (!ok) {\n await this.logout();\n }\n }, delay);\n }\n\n private clearRefreshTimer(): void {\n if (this.refreshTimer) {\n clearTimeout(this.refreshTimer);\n this.refreshTimer = null;\n }\n }\n\n private async tryRefreshWithRetry(retries: number, retryDelayMs: number): Promise<boolean> {\n try {\n await this.refresh();\n return true;\n } catch {\n if (retries > 0) {\n await new Promise((resolve) => setTimeout(resolve, retryDelayMs));\n return this.tryRefreshWithRetry(retries - 1, retryDelayMs);\n }\n\n return false;\n }\n }\n}\n\nfunction safeJsonParse(raw: string): Partial<TokenIdpAuthState> | null {\n try {\n return JSON.parse(raw) as Partial<TokenIdpAuthState>;\n } catch {\n return null;\n }\n}\n","import { CommonModule } from '@angular/common';\nimport { Component, Input, OnInit, inject } from '@angular/core';\nimport { Router } from '@angular/router';\nimport { TokenIdpAuthService } from './auth.service';\n\n@Component({\n selector: 'tokenidp-auth-callback',\n standalone: true,\n imports: [CommonModule],\n template: `\n <div class=\"idp-login-page\">\n <section class=\"idp-login-section-container\">\n <div class=\"idp-login-section idp-redirect-card\">\n <div class=\"idp-content\">\n <ng-container *ngIf=\"logoUrl; else fallbackLogo\">\n <div class=\"idp-logo-image-wrap\">\n <img [src]=\"logoUrl\" [alt]=\"logoAlt\" width=\"250\" />\n </div>\n </ng-container>\n <ng-template #fallbackLogo>\n <div class=\"idp-logo\">{{ fallbackBadge }}</div>\n </ng-template>\n <h1>Completing sign-in</h1>\n <p>{{ error || 'Please wait while we finish authentication.' }}</p>\n </div>\n </div>\n </section>\n </div>\n `,\n styles: [\n `\n .idp-login-page {\n min-height: 100vh;\n display: grid;\n place-items: center;\n background: #f5f7fb;\n color: #151923;\n font-family:\n Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n }\n\n .idp-login-section-container {\n width: min(440px, calc(100vw - 32px));\n }\n\n .idp-login-section {\n background: #fff;\n border: 1px solid #e4e8f0;\n border-radius: 8px;\n box-shadow: 0 18px 45px rgba(15, 23, 42, 0.08);\n }\n\n .idp-content {\n padding: 32px;\n text-align: center;\n }\n\n .idp-logo,\n .idp-logo-image-wrap {\n margin: 0 auto 16px;\n }\n\n .idp-logo {\n width: 56px;\n height: 56px;\n display: grid;\n place-items: center;\n border-radius: 8px;\n background: #111827;\n color: #fff;\n font-weight: 700;\n }\n\n h1 {\n margin: 0 0 8px;\n font-size: 24px;\n line-height: 1.25;\n }\n\n p {\n margin: 0;\n color: #667085;\n line-height: 1.5;\n }\n `,\n ],\n})\nexport class IdpAuthCallbackComponent implements OnInit {\n @Input() redirectTo = '';\n @Input() logoUrl = '';\n @Input() logoAlt = 'Application logo';\n @Input() fallbackBadge = 'ID';\n\n protected error = '';\n\n private readonly auth = inject(TokenIdpAuthService);\n private readonly router = inject(Router);\n\n async ngOnInit(): Promise<void> {\n const qs = new URLSearchParams(window.location.search);\n const code = qs.get('code');\n const state = qs.get('state');\n const err = qs.get('error');\n const errDesc = qs.get('error_description');\n\n if (err) {\n this.setError(errDesc || err || 'Authentication error.');\n return;\n }\n\n if (!code || !state) {\n this.setError('Missing authorization code or state.');\n return;\n }\n\n try {\n await this.auth.handleCallback({ code, state });\n await this.router.navigateByUrl(this.redirectTo || this.auth.landingPage() || '/', {\n replaceUrl: true,\n });\n } catch (error) {\n this.setError(error instanceof Error ? error.message : 'Login callback failed.');\n }\n }\n\n private setError(message: string): void {\n this.error = message;\n this.auth.setError(message);\n }\n}\n","import { CommonModule } from '@angular/common';\nimport { Component, Input, OnInit, inject } from '@angular/core';\nimport { TokenIdpAuthService } from './auth.service';\nimport { TokenIdpLoginOptions } from './models';\n\n@Component({\n selector: 'tokenidp-login',\n standalone: true,\n imports: [CommonModule],\n template: `\n <div class=\"idp-login-page\">\n <section class=\"idp-login-section-container\">\n <div class=\"idp-login-section idp-redirect-card\">\n <div class=\"idp-content\">\n <ng-container *ngIf=\"logoUrl; else fallbackLogo\">\n <div class=\"idp-logo-image-wrap\">\n <img [src]=\"logoUrl\" [alt]=\"logoAlt\" width=\"250\" />\n </div>\n </ng-container>\n <ng-template #fallbackLogo>\n <div class=\"idp-logo\">{{ fallbackBadge }}</div>\n </ng-template>\n\n <ng-container *ngIf=\"isLoggedOut; else redirecting\">\n <h1>{{ signedOutTitle }}</h1>\n <p>{{ signedOutSubtitle }}</p>\n <button type=\"button\" class=\"idp-button\" (click)=\"signInAgain()\">\n {{ signInAgainLabel }}\n </button>\n </ng-container>\n\n <ng-template #redirecting>\n <h1>{{ title }}</h1>\n <p>{{ subtitle }}</p>\n <div class=\"idp-spinner\" aria-hidden=\"true\"></div>\n </ng-template>\n </div>\n </div>\n </section>\n </div>\n `,\n styles: [\n `\n .idp-login-page {\n min-height: 100vh;\n display: grid;\n place-items: center;\n background: #f5f7fb;\n color: #151923;\n font-family:\n Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n }\n\n .idp-login-section-container {\n width: min(440px, calc(100vw - 32px));\n }\n\n .idp-login-section {\n background: #fff;\n border: 1px solid #e4e8f0;\n border-radius: 8px;\n box-shadow: 0 18px 45px rgba(15, 23, 42, 0.08);\n }\n\n .idp-content {\n padding: 32px;\n text-align: center;\n }\n\n .idp-logo,\n .idp-logo-image-wrap {\n margin: 0 auto 16px;\n }\n\n .idp-logo {\n width: 56px;\n height: 56px;\n display: grid;\n place-items: center;\n border-radius: 8px;\n background: #111827;\n color: #fff;\n font-weight: 700;\n }\n\n h1 {\n margin: 0 0 8px;\n font-size: 24px;\n line-height: 1.25;\n }\n\n p {\n margin: 0;\n color: #667085;\n line-height: 1.5;\n }\n\n .idp-button {\n margin-top: 20px;\n min-height: 40px;\n border: 0;\n border-radius: 6px;\n padding: 0 16px;\n background: #0f62fe;\n color: #fff;\n font-weight: 600;\n cursor: pointer;\n }\n\n .idp-spinner {\n width: 28px;\n height: 28px;\n border: 3px solid #d0d5dd;\n border-top-color: #0f62fe;\n border-radius: 50%;\n margin: 20px auto 0;\n animation: idp-spin 0.8s linear infinite;\n }\n\n @keyframes idp-spin {\n to {\n transform: rotate(360deg);\n }\n }\n `,\n ],\n})\nexport class IdpLoginComponent implements OnInit {\n @Input() logoUrl = '';\n @Input() logoAlt = 'Application logo';\n @Input() title = 'Redirecting to sign-in...';\n @Input() subtitle = 'Please wait while we securely connect to Identity.';\n @Input() signedOutTitle = 'You have been signed out';\n @Input() signedOutSubtitle = 'Start a new session when you are ready.';\n @Input() signInAgainLabel = 'Sign in again';\n @Input() fallbackBadge = 'ID';\n @Input() loginOptions?: TokenIdpLoginOptions;\n\n protected isLoggedOut = false;\n\n private readonly auth = inject(TokenIdpAuthService);\n\n ngOnInit(): void {\n this.isLoggedOut = new URLSearchParams(window.location.search).get('logged_out') === '1';\n\n if (!this.isLoggedOut) {\n void this.auth.login(this.loginOptions);\n }\n }\n\n protected signInAgain(): void {\n void this.auth.login(this.loginOptions);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tokenidp-angular",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Angular SDK for integrating TokenIDP OAuth 2.1 + OpenID Connect authentication.",
|
|
5
|
+
"author": "",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"keywords": [
|
|
9
|
+
"angular",
|
|
10
|
+
"oauth2",
|
|
11
|
+
"openid-connect",
|
|
12
|
+
"pkce",
|
|
13
|
+
"tokenidp"
|
|
14
|
+
],
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/naeemraza-dev/tokenidp.git",
|
|
18
|
+
"directory": "sdks/angular-idp-sdk"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/naeemraza-dev/tokenidp#readme",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/naeemraza-dev/tokenidp/issues"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@angular/common": "^21.2.0",
|
|
29
|
+
"@angular/core": "^21.2.0",
|
|
30
|
+
"@angular/router": "^21.2.0",
|
|
31
|
+
"rxjs": "^6.5.3 || ^7.4.0"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"tslib": "^2.8.1"
|
|
35
|
+
},
|
|
36
|
+
"module": "fesm2022/tokenidp-angular.mjs",
|
|
37
|
+
"typings": "types/tokenidp-angular.d.ts",
|
|
38
|
+
"exports": {
|
|
39
|
+
"./package.json": {
|
|
40
|
+
"default": "./package.json"
|
|
41
|
+
},
|
|
42
|
+
".": {
|
|
43
|
+
"types": "./types/tokenidp-angular.d.ts",
|
|
44
|
+
"default": "./fesm2022/tokenidp-angular.mjs"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"type": "module"
|
|
48
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import * as _angular_core from '@angular/core';
|
|
2
|
+
import { OnInit, InjectionToken, EnvironmentProviders } from '@angular/core';
|
|
3
|
+
|
|
4
|
+
declare class IdpAuthCallbackComponent implements OnInit {
|
|
5
|
+
redirectTo: string;
|
|
6
|
+
logoUrl: string;
|
|
7
|
+
logoAlt: string;
|
|
8
|
+
fallbackBadge: string;
|
|
9
|
+
protected error: string;
|
|
10
|
+
private readonly auth;
|
|
11
|
+
private readonly router;
|
|
12
|
+
ngOnInit(): Promise<void>;
|
|
13
|
+
private setError;
|
|
14
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<IdpAuthCallbackComponent, never>;
|
|
15
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<IdpAuthCallbackComponent, "tokenidp-auth-callback", never, { "redirectTo": { "alias": "redirectTo"; "required": false; }; "logoUrl": { "alias": "logoUrl"; "required": false; }; "logoAlt": { "alias": "logoAlt"; "required": false; }; "fallbackBadge": { "alias": "fallbackBadge"; "required": false; }; }, {}, never, never, true, never>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type TokenIdpStorageMode = 'memory' | 'sessionStorage' | 'localStorage';
|
|
19
|
+
type TokenIdpTenantPropagationMode = 'all' | 'api' | 'none';
|
|
20
|
+
interface TokenIdpAuthConfig {
|
|
21
|
+
authority: string;
|
|
22
|
+
clientId: string;
|
|
23
|
+
tenantKey?: string;
|
|
24
|
+
tenantPropagationMode?: TokenIdpTenantPropagationMode;
|
|
25
|
+
tenantQueryParameter?: string;
|
|
26
|
+
tenantHeaderName?: string;
|
|
27
|
+
tenantKeyStorageKey?: string;
|
|
28
|
+
redirectUri: string;
|
|
29
|
+
postLoginRedirectUri?: string;
|
|
30
|
+
postLogoutRedirectUri?: string;
|
|
31
|
+
scope?: string;
|
|
32
|
+
audience?: string;
|
|
33
|
+
authorizePath?: string;
|
|
34
|
+
tokenPath?: string;
|
|
35
|
+
revokePath?: string;
|
|
36
|
+
logoutPath?: string;
|
|
37
|
+
storage?: TokenIdpStorageMode;
|
|
38
|
+
storageKey?: string;
|
|
39
|
+
pkceVerifierKey?: string;
|
|
40
|
+
oauthStateKey?: string;
|
|
41
|
+
autoRefresh?: boolean;
|
|
42
|
+
refreshSkewSeconds?: number;
|
|
43
|
+
}
|
|
44
|
+
interface TokenIdpLoginOptions {
|
|
45
|
+
prompt?: string;
|
|
46
|
+
loginHint?: string;
|
|
47
|
+
audience?: string;
|
|
48
|
+
tenantKey?: string;
|
|
49
|
+
}
|
|
50
|
+
interface TokenIdpCallbackParams {
|
|
51
|
+
code: string;
|
|
52
|
+
state: string;
|
|
53
|
+
}
|
|
54
|
+
interface TokenIdpAuthState {
|
|
55
|
+
isAuthenticated: boolean;
|
|
56
|
+
tenantKey: string;
|
|
57
|
+
landingPage: string;
|
|
58
|
+
accessToken: string;
|
|
59
|
+
refreshToken: string;
|
|
60
|
+
idToken: string;
|
|
61
|
+
expiresAt: number;
|
|
62
|
+
error: string;
|
|
63
|
+
}
|
|
64
|
+
interface TokenIdpTokenResult {
|
|
65
|
+
tenantKey: string;
|
|
66
|
+
accessToken: string;
|
|
67
|
+
refreshToken: string;
|
|
68
|
+
idToken: string;
|
|
69
|
+
expiresAt: number;
|
|
70
|
+
}
|
|
71
|
+
interface TokenIdpTokenParts {
|
|
72
|
+
accessToken: string;
|
|
73
|
+
refreshToken: string;
|
|
74
|
+
expiresIn: number;
|
|
75
|
+
idToken: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
declare const defaultAuthConfig: TokenIdpAuthConfig;
|
|
79
|
+
declare const TOKEN_IDP_AUTH_CONFIG: InjectionToken<TokenIdpAuthConfig>;
|
|
80
|
+
declare function provideTokenIdpAuth(config: Partial<TokenIdpAuthConfig>): EnvironmentProviders;
|
|
81
|
+
|
|
82
|
+
declare class TokenIdpAuthService {
|
|
83
|
+
private readonly injectedConfig;
|
|
84
|
+
private readonly config;
|
|
85
|
+
private readonly platformId;
|
|
86
|
+
private readonly document;
|
|
87
|
+
private readonly isBrowser;
|
|
88
|
+
private readonly storage;
|
|
89
|
+
private refreshTimer;
|
|
90
|
+
private refreshInFlight;
|
|
91
|
+
private readonly stateSignal;
|
|
92
|
+
readonly state: _angular_core.Signal<TokenIdpAuthState>;
|
|
93
|
+
readonly isAuthenticated: _angular_core.Signal<boolean>;
|
|
94
|
+
readonly accessToken: _angular_core.Signal<string>;
|
|
95
|
+
readonly refreshToken: _angular_core.Signal<string>;
|
|
96
|
+
readonly idToken: _angular_core.Signal<string>;
|
|
97
|
+
readonly expiresAt: _angular_core.Signal<number>;
|
|
98
|
+
readonly tenantKey: _angular_core.Signal<string>;
|
|
99
|
+
readonly error: _angular_core.Signal<string>;
|
|
100
|
+
readonly landingPage: _angular_core.Signal<string>;
|
|
101
|
+
constructor();
|
|
102
|
+
login(options?: TokenIdpLoginOptions): Promise<void>;
|
|
103
|
+
logout(): Promise<void>;
|
|
104
|
+
handleCallback(params: TokenIdpCallbackParams): Promise<TokenIdpTokenResult>;
|
|
105
|
+
refresh(): Promise<Omit<TokenIdpTokenResult, 'tenantKey'>>;
|
|
106
|
+
setError(message: string): void;
|
|
107
|
+
private updateState;
|
|
108
|
+
private buildInitialState;
|
|
109
|
+
private persistState;
|
|
110
|
+
private createStorage;
|
|
111
|
+
private clearLocalSession;
|
|
112
|
+
private scheduleAutoRefresh;
|
|
113
|
+
private clearRefreshTimer;
|
|
114
|
+
private tryRefreshWithRetry;
|
|
115
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<TokenIdpAuthService, never>;
|
|
116
|
+
static ɵprov: _angular_core.ɵɵInjectableDeclaration<TokenIdpAuthService>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
declare class IdpLoginComponent implements OnInit {
|
|
120
|
+
logoUrl: string;
|
|
121
|
+
logoAlt: string;
|
|
122
|
+
title: string;
|
|
123
|
+
subtitle: string;
|
|
124
|
+
signedOutTitle: string;
|
|
125
|
+
signedOutSubtitle: string;
|
|
126
|
+
signInAgainLabel: string;
|
|
127
|
+
fallbackBadge: string;
|
|
128
|
+
loginOptions?: TokenIdpLoginOptions;
|
|
129
|
+
protected isLoggedOut: boolean;
|
|
130
|
+
private readonly auth;
|
|
131
|
+
ngOnInit(): void;
|
|
132
|
+
protected signInAgain(): void;
|
|
133
|
+
static ɵfac: _angular_core.ɵɵFactoryDeclaration<IdpLoginComponent, never>;
|
|
134
|
+
static ɵcmp: _angular_core.ɵɵComponentDeclaration<IdpLoginComponent, "tokenidp-login", never, { "logoUrl": { "alias": "logoUrl"; "required": false; }; "logoAlt": { "alias": "logoAlt"; "required": false; }; "title": { "alias": "title"; "required": false; }; "subtitle": { "alias": "subtitle"; "required": false; }; "signedOutTitle": { "alias": "signedOutTitle"; "required": false; }; "signedOutSubtitle": { "alias": "signedOutSubtitle"; "required": false; }; "signInAgainLabel": { "alias": "signInAgainLabel"; "required": false; }; "fallbackBadge": { "alias": "fallbackBadge"; "required": false; }; "loginOptions": { "alias": "loginOptions"; "required": false; }; }, {}, never, never, true, never>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export { IdpAuthCallbackComponent, IdpLoginComponent, TOKEN_IDP_AUTH_CONFIG, TokenIdpAuthService, defaultAuthConfig, provideTokenIdpAuth };
|
|
138
|
+
export type { TokenIdpAuthConfig, TokenIdpAuthState, TokenIdpCallbackParams, TokenIdpLoginOptions, TokenIdpStorageMode, TokenIdpTenantPropagationMode, TokenIdpTokenParts, TokenIdpTokenResult };
|