@ratespecial/logto-angular 1.0.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 +507 -0
- package/fesm2022/ratespecial-logto-angular-testing.mjs +49 -0
- package/fesm2022/ratespecial-logto-angular-testing.mjs.map +1 -0
- package/fesm2022/ratespecial-logto-angular.mjs +396 -0
- package/fesm2022/ratespecial-logto-angular.mjs.map +1 -0
- package/package.json +42 -0
- package/types/ratespecial-logto-angular-testing.d.ts +15 -0
- package/types/ratespecial-logto-angular.d.ts +270 -0
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, Injectable, makeEnvironmentProviders, provideAppInitializer, signal, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { Router, RouterLink, RoutesRecognized } from '@angular/router';
|
|
4
|
+
import { BehaviorSubject, isObservable, from, switchMap } from 'rxjs';
|
|
5
|
+
import { distinctUntilChanged, tap, filter } from 'rxjs/operators';
|
|
6
|
+
import LogtoClient from '@logto/browser';
|
|
7
|
+
import { HttpErrorResponse } from '@angular/common/http';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* The resolved auth configuration (native Logto config + `routing` addon). Every part of
|
|
11
|
+
* the library reads from this token, so nothing depends on the consuming app's environment.
|
|
12
|
+
*/
|
|
13
|
+
const LOGTO_AUTH_CONFIG = new InjectionToken('LOGTO_AUTH_CONFIG');
|
|
14
|
+
/**
|
|
15
|
+
* The single, app-wide `@logto/browser` client. One client handles every resource:
|
|
16
|
+
* `getAccessToken(resource)` exchanges the shared refresh token for a resource-scoped JWT
|
|
17
|
+
* on demand and caches it.
|
|
18
|
+
*/
|
|
19
|
+
const LOGTO_CLIENT = new InjectionToken('LOGTO_CLIENT');
|
|
20
|
+
/**
|
|
21
|
+
* The primary API resource (`routing.primaryResource`, or the first secure-route resource).
|
|
22
|
+
* Used where a specific resource token is needed outside the HTTP interceptor — e.g. the
|
|
23
|
+
* Echo/Pusher auth header and the post-callback access gate.
|
|
24
|
+
*/
|
|
25
|
+
const PRIMARY_RESOURCE = new InjectionToken('PRIMARY_RESOURCE');
|
|
26
|
+
/**
|
|
27
|
+
* Multi-provider token that collects `AuthLogoutHook` callbacks invoked during logout.
|
|
28
|
+
*
|
|
29
|
+
* Ordering between hooks follows Angular DI registration order. Register one hook per concern
|
|
30
|
+
* (state reset, cache flush, telemetry, …) rather than bundling them.
|
|
31
|
+
*
|
|
32
|
+
* Prefer registering hooks via `provideLogtoAuth({ logoutHookFactories: [...] })` over wiring
|
|
33
|
+
* this token directly — the factory form runs inside an injection context so the hook can
|
|
34
|
+
* use `inject()`.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* provideLogtoAuth({
|
|
39
|
+
* endpoint: environment.logto.endpoint,
|
|
40
|
+
* appId: environment.logto.appId,
|
|
41
|
+
* routing: environment.logto.routing,
|
|
42
|
+
* logoutHookFactories: [
|
|
43
|
+
* () => {
|
|
44
|
+
* const store = inject(Store);
|
|
45
|
+
* return () => store.dispatch(new ClearState());
|
|
46
|
+
* },
|
|
47
|
+
* ],
|
|
48
|
+
* })
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
const AUTH_LOGOUT_HOOK = new InjectionToken('AUTH_LOGOUT_HOOK');
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Angular-friendly facade over the promise-based `@logto/browser` client. Owns the
|
|
55
|
+
* authenticated-state signal, sign-in/out, and resource-scoped token access.
|
|
56
|
+
*/
|
|
57
|
+
class AuthService {
|
|
58
|
+
router = inject(Router);
|
|
59
|
+
client = inject(LOGTO_CLIENT);
|
|
60
|
+
routing = inject(LOGTO_AUTH_CONFIG).routing;
|
|
61
|
+
logoutHooks = inject(AUTH_LOGOUT_HOOK, { optional: true }) ?? [];
|
|
62
|
+
authenticated$ = new BehaviorSubject(false);
|
|
63
|
+
/**
|
|
64
|
+
* Authenticated state as a stream. Backed by a `BehaviorSubject`, so it replays the current
|
|
65
|
+
* value on subscribe (order-independent for late subscribers) and only emits on change.
|
|
66
|
+
*/
|
|
67
|
+
isAuthenticated$ = this.authenticated$.pipe(distinctUntilChanged());
|
|
68
|
+
/**
|
|
69
|
+
* Re-reads the client's authenticated state (checks for a stored session; no network) and
|
|
70
|
+
* pushes it to the stream. Returns the resolved value.
|
|
71
|
+
*/
|
|
72
|
+
async refreshAuthState() {
|
|
73
|
+
const authenticated = await this.client.isAuthenticated();
|
|
74
|
+
this.authenticated$.next(authenticated);
|
|
75
|
+
return authenticated;
|
|
76
|
+
}
|
|
77
|
+
/** Begin a sign-in flow with Logto (full-page redirect to the hosted UI). */
|
|
78
|
+
signIn(redirectUri = window.location.origin + this.routing.callbackPath) {
|
|
79
|
+
void this.client.signIn(redirectUri);
|
|
80
|
+
}
|
|
81
|
+
/** Complete the OAuth callback, then refresh local auth state. */
|
|
82
|
+
async handleCallback(callbackUri) {
|
|
83
|
+
await this.client.handleSignInCallback(callbackUri);
|
|
84
|
+
await this.refreshAuthState();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Fetch a resource-scoped JWT. The client refreshes/caches transparently, so this is safe to
|
|
88
|
+
* call on every request. Omit `resource` for an opaque (userinfo) token.
|
|
89
|
+
*/
|
|
90
|
+
getAccessToken(resource) {
|
|
91
|
+
return this.client.getAccessToken(resource);
|
|
92
|
+
}
|
|
93
|
+
/** Decoded claims of the resource-scoped access token (e.g. to inspect `scope`). */
|
|
94
|
+
getAccessTokenClaims(resource) {
|
|
95
|
+
return this.client.getAccessTokenClaims(resource);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Decoded claims of the ID token (e.g. `sub`, `name`, `email`, `picture`). Reads the token
|
|
99
|
+
* already in storage and decodes it locally — no network call.
|
|
100
|
+
*/
|
|
101
|
+
getIdTokenClaims() {
|
|
102
|
+
return this.client.getIdTokenClaims();
|
|
103
|
+
}
|
|
104
|
+
/** Sign out via Logto and reset local state. */
|
|
105
|
+
logout() {
|
|
106
|
+
this.fireLogoutHooks();
|
|
107
|
+
this.authenticated$.next(false);
|
|
108
|
+
const signedOutPath = this.routing.signedOutPath;
|
|
109
|
+
this.client.signOut(window.location.origin + signedOutPath).catch((err) => {
|
|
110
|
+
console.error(err);
|
|
111
|
+
if (this.router.url !== signedOutPath) {
|
|
112
|
+
this.router.navigateByUrl(signedOutPath);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
fireLogoutHooks() {
|
|
117
|
+
for (const hook of this.logoutHooks) {
|
|
118
|
+
const result = hook();
|
|
119
|
+
if (isObservable(result)) {
|
|
120
|
+
result.subscribe({ error: (err) => console.error(err) });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
125
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuthService, providedIn: 'root' });
|
|
126
|
+
}
|
|
127
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: AuthService, decorators: [{
|
|
128
|
+
type: Injectable,
|
|
129
|
+
args: [{
|
|
130
|
+
providedIn: 'root',
|
|
131
|
+
}]
|
|
132
|
+
}] });
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* sessionStorage key used to persist the last visited app route across the OIDC
|
|
136
|
+
* redirect round-trip. Scoped to the browser session (cleared on tab close) and
|
|
137
|
+
* namespaced under `auth.` to avoid collision with unrelated app state.
|
|
138
|
+
*/
|
|
139
|
+
const STORAGE_KEY = 'auth.lastVisitedRoute';
|
|
140
|
+
/**
|
|
141
|
+
* Facade for tracking the last visited route, used to restore navigation after login.
|
|
142
|
+
* Backed by sessionStorage so the value is scoped to the browser session.
|
|
143
|
+
*/
|
|
144
|
+
class HistoryService {
|
|
145
|
+
setLastVisitedRoute(route) {
|
|
146
|
+
try {
|
|
147
|
+
sessionStorage.setItem(STORAGE_KEY, route);
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
console.error(err);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
getLastVisitedRoute() {
|
|
154
|
+
try {
|
|
155
|
+
return sessionStorage.getItem(STORAGE_KEY);
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
console.error(err);
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
clearLastVisitedRoute() {
|
|
163
|
+
try {
|
|
164
|
+
sessionStorage.removeItem(STORAGE_KEY);
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
console.error(err);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
consumeLastVisitedRoute() {
|
|
171
|
+
const url = this.getLastVisitedRoute();
|
|
172
|
+
this.clearLastVisitedRoute();
|
|
173
|
+
return url;
|
|
174
|
+
}
|
|
175
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: HistoryService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
176
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: HistoryService, providedIn: 'root' });
|
|
177
|
+
}
|
|
178
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: HistoryService, decorators: [{
|
|
179
|
+
type: Injectable,
|
|
180
|
+
args: [{
|
|
181
|
+
providedIn: 'root',
|
|
182
|
+
}]
|
|
183
|
+
}] });
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Wires up the auth library: provides the resolved config, constructs the single
|
|
187
|
+
* `@logto/browser` client, resolves the primary resource, contributes any
|
|
188
|
+
* `logoutHookFactories` to the `AUTH_LOGOUT_HOOK` multi-provider, and hydrates the
|
|
189
|
+
* authenticated-state signal from any existing session before the first guard runs.
|
|
190
|
+
*
|
|
191
|
+
* Returned providers are environment-scoped — call this once at the root provider list.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```ts
|
|
195
|
+
* providers: [
|
|
196
|
+
* provideLogtoAuth({
|
|
197
|
+
* ...environment.logto,
|
|
198
|
+
* logoutHookFactories: [
|
|
199
|
+
* () => {
|
|
200
|
+
* const store = inject(Store);
|
|
201
|
+
* return () => store.dispatch(new ClearState());
|
|
202
|
+
* },
|
|
203
|
+
* ],
|
|
204
|
+
* }),
|
|
205
|
+
* ]
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
function provideLogtoAuth(options) {
|
|
209
|
+
const { logoutHookFactories, ...config } = options;
|
|
210
|
+
const hookProviders = (logoutHookFactories ?? []).map((factory) => ({
|
|
211
|
+
provide: AUTH_LOGOUT_HOOK,
|
|
212
|
+
multi: true,
|
|
213
|
+
useFactory: factory,
|
|
214
|
+
}));
|
|
215
|
+
// `routing` and `noAccessMessage` are app-only concerns; strip them so only the native
|
|
216
|
+
// `LogtoConfig` reaches the SDK.
|
|
217
|
+
const { routing: _routing, noAccessMessage: _noAccessMessage, ...logtoConfig } = config;
|
|
218
|
+
return makeEnvironmentProviders([
|
|
219
|
+
{ provide: LOGTO_AUTH_CONFIG, useValue: config },
|
|
220
|
+
{ provide: LOGTO_CLIENT, useValue: new LogtoClient(logtoConfig) },
|
|
221
|
+
{
|
|
222
|
+
provide: PRIMARY_RESOURCE,
|
|
223
|
+
useValue: config.routing.primaryResource ?? config.routing.secureRoutes[0].resource,
|
|
224
|
+
},
|
|
225
|
+
...hookProviders,
|
|
226
|
+
// Hydrate the authenticated-state signal from any existing session before the first guard runs.
|
|
227
|
+
provideAppInitializer(() => {
|
|
228
|
+
void inject(AuthService).refreshAuthState();
|
|
229
|
+
}),
|
|
230
|
+
]);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const DEFAULT_NO_ACCESS_MESSAGE = 'Your account has no access to this application. Contact an administrator.';
|
|
234
|
+
class CallbackComponent {
|
|
235
|
+
authService = inject(AuthService);
|
|
236
|
+
router = inject(Router);
|
|
237
|
+
historyService = inject(HistoryService);
|
|
238
|
+
primaryResource = inject(PRIMARY_RESOURCE);
|
|
239
|
+
noAccessMessage = inject(LOGTO_AUTH_CONFIG).noAccessMessage ?? DEFAULT_NO_ACCESS_MESSAGE;
|
|
240
|
+
loading = signal(true, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
241
|
+
error = signal(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
242
|
+
async ngOnInit() {
|
|
243
|
+
try {
|
|
244
|
+
await this.authService.handleCallback(window.location.href);
|
|
245
|
+
// Gate SPA access on the primary (local API) resource token carrying scopes.
|
|
246
|
+
// Secondary resources are CORS APIs and may legitimately be empty for some users.
|
|
247
|
+
const claims = await this.authService.getAccessTokenClaims(this.primaryResource);
|
|
248
|
+
this.loading.set(false);
|
|
249
|
+
if (!(claims.scope ?? '').trim()) {
|
|
250
|
+
this.error.set(this.noAccessMessage);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const destination = this.historyService.consumeLastVisitedRoute() || '/';
|
|
254
|
+
this.router.navigateByUrl(destination);
|
|
255
|
+
}
|
|
256
|
+
catch (err) {
|
|
257
|
+
this.loading.set(false);
|
|
258
|
+
this.error.set(err instanceof Error ? err.message : 'Authentication failed.');
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: CallbackComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
262
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: CallbackComponent, isStandalone: true, selector: "lib-callback", ngImport: i0, template: "<div class=\"callback-container\">\n @if (loading()) {\n <div class=\"loading\">\n <div class=\"spinner\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n <p class=\"loading-text\">Completing sign-in, please wait...</p>\n </div>\n }\n\n @if (!loading() && error()) {\n <div class=\"error\" role=\"alert\">\n {{ error() }}\n </div>\n\n <a class=\"return-link\" routerLink=\"/\">Return to Login</a>\n }\n</div>\n", styles: [":host{display:block}.callback-container{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:60vh}.loading{display:flex;flex-direction:column;align-items:center}.spinner{width:2rem;height:2rem;margin-bottom:1rem;border:.25em solid currentcolor;border-right-color:transparent;border-radius:50%;color:#0d6efd;animation:spinner-border .75s linear infinite}@keyframes spinner-border{to{transform:rotate(360deg)}}.visually-hidden{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.loading-text{font-weight:700}.error{width:100%;padding:1rem;text-align:center;color:#58151c;background-color:#f8d7da;border:1px solid #f1aeb5;border-radius:.375rem}.return-link{display:inline-block;margin-top:.5rem;padding:.375rem .75rem;font-weight:400;line-height:1.5;text-align:center;text-decoration:none;color:#0d6efd;background-color:transparent;border:1px solid #0d6efd;border-radius:.375rem;cursor:pointer;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}.return-link:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
263
|
+
}
|
|
264
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: CallbackComponent, decorators: [{
|
|
265
|
+
type: Component,
|
|
266
|
+
args: [{ selector: 'lib-callback', imports: [RouterLink], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"callback-container\">\n @if (loading()) {\n <div class=\"loading\">\n <div class=\"spinner\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n <p class=\"loading-text\">Completing sign-in, please wait...</p>\n </div>\n }\n\n @if (!loading() && error()) {\n <div class=\"error\" role=\"alert\">\n {{ error() }}\n </div>\n\n <a class=\"return-link\" routerLink=\"/\">Return to Login</a>\n }\n</div>\n", styles: [":host{display:block}.callback-container{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:60vh}.loading{display:flex;flex-direction:column;align-items:center}.spinner{width:2rem;height:2rem;margin-bottom:1rem;border:.25em solid currentcolor;border-right-color:transparent;border-radius:50%;color:#0d6efd;animation:spinner-border .75s linear infinite}@keyframes spinner-border{to{transform:rotate(360deg)}}.visually-hidden{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.loading-text{font-weight:700}.error{width:100%;padding:1rem;text-align:center;color:#58151c;background-color:#f8d7da;border:1px solid #f1aeb5;border-radius:.375rem}.return-link{display:inline-block;margin-top:.5rem;padding:.375rem .75rem;font-weight:400;line-height:1.5;text-align:center;text-decoration:none;color:#0d6efd;background-color:transparent;border:1px solid #0d6efd;border-radius:.375rem;cursor:pointer;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}.return-link:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}\n"] }]
|
|
267
|
+
}] });
|
|
268
|
+
|
|
269
|
+
class SignedOutComponent {
|
|
270
|
+
authService = inject(AuthService);
|
|
271
|
+
/** Restart the Logto sign-in flow (redirects to the hosted UI). */
|
|
272
|
+
signIn() {
|
|
273
|
+
this.authService.signIn();
|
|
274
|
+
}
|
|
275
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: SignedOutComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
276
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.12", type: SignedOutComponent, isStandalone: true, selector: "lib-signed-out", ngImport: i0, template: "<div class=\"signed-out-container\">\n <div class=\"card\">\n <h2 class=\"title\">Signed out</h2>\n <p class=\"message\">You have been signed out.</p>\n <button type=\"button\" class=\"sign-in-button\" (click)=\"signIn()\">Sign in again</button>\n </div>\n</div>\n", styles: [":host{display:block}.signed-out-container{display:flex;align-items:center;justify-content:center;min-height:100vh}.card{min-width:350px;padding:1.5rem;background-color:#fff;border:1px solid rgba(0,0,0,.175);border-radius:.375rem;box-shadow:0 .5rem 1rem #00000026}.title,.message{margin-bottom:1.5rem;text-align:center}.sign-in-button{width:100%;padding:.375rem .75rem;font-weight:400;line-height:1.5;text-align:center;color:#fff;background-color:#0d6efd;border:1px solid #0d6efd;border-radius:.375rem;cursor:pointer;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}.sign-in-button:hover{background-color:#0b5ed7;border-color:#0a58ca}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
277
|
+
}
|
|
278
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: SignedOutComponent, decorators: [{
|
|
279
|
+
type: Component,
|
|
280
|
+
args: [{ selector: 'lib-signed-out', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"signed-out-container\">\n <div class=\"card\">\n <h2 class=\"title\">Signed out</h2>\n <p class=\"message\">You have been signed out.</p>\n <button type=\"button\" class=\"sign-in-button\" (click)=\"signIn()\">Sign in again</button>\n </div>\n</div>\n", styles: [":host{display:block}.signed-out-container{display:flex;align-items:center;justify-content:center;min-height:100vh}.card{min-width:350px;padding:1.5rem;background-color:#fff;border:1px solid rgba(0,0,0,.175);border-radius:.375rem;box-shadow:0 .5rem 1rem #00000026}.title,.message{margin-bottom:1.5rem;text-align:center}.sign-in-button{width:100%;padding:.375rem .75rem;font-weight:400;line-height:1.5;text-align:center;color:#fff;background-color:#0d6efd;border:1px solid #0d6efd;border-radius:.375rem;cursor:pointer;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}.sign-in-button:hover{background-color:#0b5ed7;border-color:#0a58ca}\n"] }]
|
|
281
|
+
}] });
|
|
282
|
+
|
|
283
|
+
/** Routes accept a path relative to the app root, so drop any leading slash. */
|
|
284
|
+
function toRoutePath(path) {
|
|
285
|
+
return path.replace(/^\//, '');
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Returns the auth routes (callback + signed-out landing) for the configured paths. Spread the
|
|
289
|
+
* result into the app's top-level `Routes`. Passing the same `routing` config used by
|
|
290
|
+
* `provideLogtoAuth()` keeps the route definitions and the redirect URIs from drifting apart.
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```ts
|
|
294
|
+
* export const routes: Routes = [
|
|
295
|
+
* {path: '', pathMatch: 'full', redirectTo: '/dashboard'},
|
|
296
|
+
* ...getAuthRoutes(environment.logto.routing),
|
|
297
|
+
* // ...app routes
|
|
298
|
+
* ];
|
|
299
|
+
* ```
|
|
300
|
+
*/
|
|
301
|
+
function getAuthRoutes(routing) {
|
|
302
|
+
return [
|
|
303
|
+
{ path: toRoutePath(routing.callbackPath), component: CallbackComponent },
|
|
304
|
+
{ path: toRoutePath(routing.signedOutPath), component: SignedOutComponent },
|
|
305
|
+
];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Replaces `autoLoginPartialRoutesGuard`. Allows activation when a Logto session
|
|
310
|
+
* exists; otherwise records the attempted route and kicks off a sign-in redirect.
|
|
311
|
+
*/
|
|
312
|
+
const authGuard = async (_route, state) => {
|
|
313
|
+
const auth = inject(AuthService);
|
|
314
|
+
const history = inject(HistoryService);
|
|
315
|
+
if (await auth.refreshAuthState()) {
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
if (!state.url.startsWith('/auth')) {
|
|
319
|
+
history.setLastVisitedRoute(state.url);
|
|
320
|
+
}
|
|
321
|
+
auth.signIn();
|
|
322
|
+
return false;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Picks the configured resource whose `routes` match a request URL, so the interceptor can
|
|
327
|
+
* fetch the correct resource-scoped token. Returns `undefined` when no resource matches (the
|
|
328
|
+
* request goes out without an Authorization header).
|
|
329
|
+
*/
|
|
330
|
+
function resourceForUrl(url, secureRoutes) {
|
|
331
|
+
const match = secureRoutes.find((mapping) => mapping.routes.some((route) => url.startsWith(route)));
|
|
332
|
+
return match?.resource;
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Matches the outgoing URL against each configured resource's `routes`; on a match it fetches
|
|
336
|
+
* that resource's access token (cached/refreshed by the Logto client) and attaches it as a
|
|
337
|
+
* Bearer header. Requests that match no resource pass through unauthenticated.
|
|
338
|
+
*/
|
|
339
|
+
const logtoTokenInterceptor = (req, next) => {
|
|
340
|
+
const { routing } = inject(LOGTO_AUTH_CONFIG);
|
|
341
|
+
const resource = resourceForUrl(req.url, routing.secureRoutes);
|
|
342
|
+
if (!resource) {
|
|
343
|
+
return next(req);
|
|
344
|
+
}
|
|
345
|
+
const auth = inject(AuthService);
|
|
346
|
+
return from(auth.getAccessToken(resource)).pipe(switchMap((token) => {
|
|
347
|
+
if (token) {
|
|
348
|
+
return next(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }));
|
|
349
|
+
}
|
|
350
|
+
return next(req);
|
|
351
|
+
}));
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* If any /api call returns 401, sign the user out and send them to the login page.
|
|
356
|
+
*/
|
|
357
|
+
const logoutOnUnauthInterceptor = (req, next) => {
|
|
358
|
+
const authService = inject(AuthService);
|
|
359
|
+
return next(req).pipe(tap({
|
|
360
|
+
error: (err) => {
|
|
361
|
+
if (err instanceof HttpErrorResponse && err.status === 401) {
|
|
362
|
+
authService.logout();
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
}));
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Initializer that tracks the last visited route.
|
|
370
|
+
* Excludes auth routes (/auth/*) to prevent redirect loops.
|
|
371
|
+
*/
|
|
372
|
+
function initializeRouteTracking() {
|
|
373
|
+
return () => {
|
|
374
|
+
const router = inject(Router);
|
|
375
|
+
const historyService = inject(HistoryService);
|
|
376
|
+
router.events
|
|
377
|
+
.pipe(filter((event) => event instanceof RoutesRecognized))
|
|
378
|
+
.subscribe((event) => {
|
|
379
|
+
if (!event.urlAfterRedirects.startsWith('/auth')) {
|
|
380
|
+
historyService.setLastVisitedRoute(event.urlAfterRedirects);
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/*
|
|
387
|
+
* Public API Surface of @ratespecial/logto-angular
|
|
388
|
+
*/
|
|
389
|
+
// Services
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Generated bundle index. Do not edit.
|
|
393
|
+
*/
|
|
394
|
+
|
|
395
|
+
export { AUTH_LOGOUT_HOOK, AuthService, CallbackComponent, HistoryService, LOGTO_AUTH_CONFIG, LOGTO_CLIENT, PRIMARY_RESOURCE, SignedOutComponent, authGuard, getAuthRoutes, initializeRouteTracking, logoutOnUnauthInterceptor, logtoTokenInterceptor, provideLogtoAuth, resourceForUrl };
|
|
396
|
+
//# sourceMappingURL=ratespecial-logto-angular.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ratespecial-logto-angular.mjs","sources":["../../../projects/logto-angular/src/lib/tokens.ts","../../../projects/logto-angular/src/lib/auth.service.ts","../../../projects/logto-angular/src/lib/history.service.ts","../../../projects/logto-angular/src/lib/provide-auth.ts","../../../projects/logto-angular/src/lib/callback/callback.component.ts","../../../projects/logto-angular/src/lib/callback/callback.component.html","../../../projects/logto-angular/src/lib/signed-out/signed-out.component.ts","../../../projects/logto-angular/src/lib/signed-out/signed-out.component.html","../../../projects/logto-angular/src/lib/routes.ts","../../../projects/logto-angular/src/lib/guards/auth.guard.ts","../../../projects/logto-angular/src/lib/interceptors/logto-token.interceptor.ts","../../../projects/logto-angular/src/lib/interceptors/logout-on-unauth.interceptor.ts","../../../projects/logto-angular/src/lib/initializers/route-tracking.initializer.ts","../../../projects/logto-angular/src/public-api.ts","../../../projects/logto-angular/src/ratespecial-logto-angular.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport type LogtoClient from '@logto/browser';\nimport { Observable } from 'rxjs';\nimport { LogtoAuthConfig } from './logto.config';\n\n/**\n * The resolved auth configuration (native Logto config + `routing` addon). Every part of\n * the library reads from this token, so nothing depends on the consuming app's environment.\n */\nexport const LOGTO_AUTH_CONFIG = new InjectionToken<LogtoAuthConfig>('LOGTO_AUTH_CONFIG');\n\n/**\n * The single, app-wide `@logto/browser` client. One client handles every resource:\n * `getAccessToken(resource)` exchanges the shared refresh token for a resource-scoped JWT\n * on demand and caches it.\n */\nexport const LOGTO_CLIENT = new InjectionToken<LogtoClient>('LOGTO_CLIENT');\n\n/**\n * The primary API resource (`routing.primaryResource`, or the first secure-route resource).\n * Used where a specific resource token is needed outside the HTTP interceptor — e.g. the\n * Echo/Pusher auth header and the post-callback access gate.\n */\nexport const PRIMARY_RESOURCE = new InjectionToken<string>('PRIMARY_RESOURCE');\n\n/**\n * A side-effect callback the library runs at the start of `AuthService.logout()`, before the\n * sign-out redirect. Use for app teardown such as clearing client-side state or flushing caches.\n *\n * Return `void` for synchronous work or an `Observable` for async work. Async hooks are\n * fire-and-forget — the library does not wait for them before logging the user off.\n */\nexport type AuthLogoutHook = () => void | Observable<unknown>;\n\n/**\n * Multi-provider token that collects `AuthLogoutHook` callbacks invoked during logout.\n *\n * Ordering between hooks follows Angular DI registration order. Register one hook per concern\n * (state reset, cache flush, telemetry, …) rather than bundling them.\n *\n * Prefer registering hooks via `provideLogtoAuth({ logoutHookFactories: [...] })` over wiring\n * this token directly — the factory form runs inside an injection context so the hook can\n * use `inject()`.\n *\n * @example\n * ```ts\n * provideLogtoAuth({\n * endpoint: environment.logto.endpoint,\n * appId: environment.logto.appId,\n * routing: environment.logto.routing,\n * logoutHookFactories: [\n * () => {\n * const store = inject(Store);\n * return () => store.dispatch(new ClearState());\n * },\n * ],\n * })\n * ```\n */\nexport const AUTH_LOGOUT_HOOK = new InjectionToken<AuthLogoutHook[]>('AUTH_LOGOUT_HOOK');\n","import { inject, Injectable } from '@angular/core';\nimport { Router } from '@angular/router';\nimport type { AccessTokenClaims, IdTokenClaims } from '@logto/browser';\nimport { BehaviorSubject, isObservable, Observable } from 'rxjs';\nimport { distinctUntilChanged } from 'rxjs/operators';\nimport { AUTH_LOGOUT_HOOK, LOGTO_AUTH_CONFIG, LOGTO_CLIENT } from './tokens';\n\n/**\n * Angular-friendly facade over the promise-based `@logto/browser` client. Owns the\n * authenticated-state signal, sign-in/out, and resource-scoped token access.\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class AuthService {\n private router = inject(Router);\n private client = inject(LOGTO_CLIENT);\n private routing = inject(LOGTO_AUTH_CONFIG).routing;\n private logoutHooks = inject(AUTH_LOGOUT_HOOK, { optional: true }) ?? [];\n\n private authenticated$ = new BehaviorSubject(false);\n\n /**\n * Authenticated state as a stream. Backed by a `BehaviorSubject`, so it replays the current\n * value on subscribe (order-independent for late subscribers) and only emits on change.\n */\n readonly isAuthenticated$: Observable<boolean> = this.authenticated$.pipe(distinctUntilChanged());\n\n /**\n * Re-reads the client's authenticated state (checks for a stored session; no network) and\n * pushes it to the stream. Returns the resolved value.\n */\n async refreshAuthState(): Promise<boolean> {\n const authenticated = await this.client.isAuthenticated();\n this.authenticated$.next(authenticated);\n return authenticated;\n }\n\n /** Begin a sign-in flow with Logto (full-page redirect to the hosted UI). */\n signIn(redirectUri: string = window.location.origin + this.routing.callbackPath): void {\n void this.client.signIn(redirectUri);\n }\n\n /** Complete the OAuth callback, then refresh local auth state. */\n async handleCallback(callbackUri: string): Promise<void> {\n await this.client.handleSignInCallback(callbackUri);\n await this.refreshAuthState();\n }\n\n /**\n * Fetch a resource-scoped JWT. The client refreshes/caches transparently, so this is safe to\n * call on every request. Omit `resource` for an opaque (userinfo) token.\n */\n getAccessToken(resource?: string): Promise<string> {\n return this.client.getAccessToken(resource);\n }\n\n /** Decoded claims of the resource-scoped access token (e.g. to inspect `scope`). */\n getAccessTokenClaims(resource?: string): Promise<AccessTokenClaims> {\n return this.client.getAccessTokenClaims(resource);\n }\n\n /**\n * Decoded claims of the ID token (e.g. `sub`, `name`, `email`, `picture`). Reads the token\n * already in storage and decodes it locally — no network call.\n */\n getIdTokenClaims(): Promise<IdTokenClaims> {\n return this.client.getIdTokenClaims();\n }\n\n /** Sign out via Logto and reset local state. */\n logout(): void {\n this.fireLogoutHooks();\n\n this.authenticated$.next(false);\n\n const signedOutPath = this.routing.signedOutPath;\n this.client.signOut(window.location.origin + signedOutPath).catch((err: unknown) => {\n console.error(err);\n\n if (this.router.url !== signedOutPath) {\n this.router.navigateByUrl(signedOutPath);\n }\n });\n }\n\n protected fireLogoutHooks(): void {\n for (const hook of this.logoutHooks) {\n const result = hook();\n\n if (isObservable(result)) {\n result.subscribe({ error: (err) => console.error(err) });\n }\n }\n }\n}\n","import { Injectable } from '@angular/core';\n\n/**\n * sessionStorage key used to persist the last visited app route across the OIDC\n * redirect round-trip. Scoped to the browser session (cleared on tab close) and\n * namespaced under `auth.` to avoid collision with unrelated app state.\n */\nconst STORAGE_KEY = 'auth.lastVisitedRoute';\n\n/**\n * Facade for tracking the last visited route, used to restore navigation after login.\n * Backed by sessionStorage so the value is scoped to the browser session.\n */\n@Injectable({\n providedIn: 'root',\n})\nexport class HistoryService {\n setLastVisitedRoute(route: string): void {\n try {\n sessionStorage.setItem(STORAGE_KEY, route);\n } catch (err) {\n console.error(err);\n }\n }\n\n getLastVisitedRoute(): string | null {\n try {\n return sessionStorage.getItem(STORAGE_KEY);\n } catch (err) {\n console.error(err);\n return null;\n }\n }\n\n clearLastVisitedRoute(): void {\n try {\n sessionStorage.removeItem(STORAGE_KEY);\n } catch (err) {\n console.error(err);\n }\n }\n\n consumeLastVisitedRoute(): string | null {\n const url = this.getLastVisitedRoute();\n this.clearLastVisitedRoute();\n return url;\n }\n}\n","import {\n EnvironmentProviders,\n inject,\n makeEnvironmentProviders,\n provideAppInitializer,\n} from '@angular/core';\nimport LogtoClient from '@logto/browser';\nimport { AuthService } from './auth.service';\nimport { LogtoAuthConfig } from './logto.config';\nimport {\n AUTH_LOGOUT_HOOK,\n AuthLogoutHook,\n LOGTO_AUTH_CONFIG,\n LOGTO_CLIENT,\n PRIMARY_RESOURCE,\n} from './tokens';\n\n/**\n * Configuration passed to `provideLogtoAuth()`: the native `LogtoConfig` fields plus the\n * `routing` addon, with optional logout hooks.\n */\nexport interface LogtoAuthOptions extends LogtoAuthConfig {\n /**\n * Factories that produce `AuthLogoutHook` callbacks. Each factory runs inside an injection\n * context, so it may use `inject()` to obtain app services (e.g. an NGXS `Store`) when\n * building its hook. The hooks run at the start of `AuthService.logout()`.\n *\n * @example\n * ```ts\n * logoutHookFactories: [\n * () => {\n * const store = inject(Store);\n * return () => store.dispatch(new ClearState());\n * },\n * ]\n * ```\n */\n logoutHookFactories?: (() => AuthLogoutHook)[];\n}\n\n/**\n * Wires up the auth library: provides the resolved config, constructs the single\n * `@logto/browser` client, resolves the primary resource, contributes any\n * `logoutHookFactories` to the `AUTH_LOGOUT_HOOK` multi-provider, and hydrates the\n * authenticated-state signal from any existing session before the first guard runs.\n *\n * Returned providers are environment-scoped — call this once at the root provider list.\n *\n * @example\n * ```ts\n * providers: [\n * provideLogtoAuth({\n * ...environment.logto,\n * logoutHookFactories: [\n * () => {\n * const store = inject(Store);\n * return () => store.dispatch(new ClearState());\n * },\n * ],\n * }),\n * ]\n * ```\n */\nexport function provideLogtoAuth(options: LogtoAuthOptions): EnvironmentProviders {\n const { logoutHookFactories, ...config } = options;\n\n const hookProviders = (logoutHookFactories ?? []).map((factory) => ({\n provide: AUTH_LOGOUT_HOOK,\n multi: true,\n useFactory: factory,\n }));\n\n // `routing` and `noAccessMessage` are app-only concerns; strip them so only the native\n // `LogtoConfig` reaches the SDK.\n const { routing: _routing, noAccessMessage: _noAccessMessage, ...logtoConfig } = config;\n\n return makeEnvironmentProviders([\n { provide: LOGTO_AUTH_CONFIG, useValue: config },\n { provide: LOGTO_CLIENT, useValue: new LogtoClient(logtoConfig) },\n {\n provide: PRIMARY_RESOURCE,\n useValue: config.routing.primaryResource ?? config.routing.secureRoutes[0].resource,\n },\n\n ...hookProviders,\n\n // Hydrate the authenticated-state signal from any existing session before the first guard runs.\n provideAppInitializer(() => {\n void inject(AuthService).refreshAuthState();\n }),\n ]);\n}\n","import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core';\nimport { Router, RouterLink } from '@angular/router';\nimport { AuthService } from '../auth.service';\nimport { HistoryService } from '../history.service';\nimport { LOGTO_AUTH_CONFIG, PRIMARY_RESOURCE } from '../tokens';\n\nconst DEFAULT_NO_ACCESS_MESSAGE =\n 'Your account has no access to this application. Contact an administrator.';\n\n@Component({\n selector: 'lib-callback',\n templateUrl: './callback.component.html',\n styleUrls: ['./callback.component.scss'],\n imports: [RouterLink],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class CallbackComponent implements OnInit {\n private authService = inject(AuthService);\n private router = inject(Router);\n private historyService = inject(HistoryService);\n private primaryResource = inject(PRIMARY_RESOURCE);\n private noAccessMessage = inject(LOGTO_AUTH_CONFIG).noAccessMessage ?? DEFAULT_NO_ACCESS_MESSAGE;\n\n loading = signal(true);\n error = signal<string | null>(null);\n\n async ngOnInit(): Promise<void> {\n try {\n await this.authService.handleCallback(window.location.href);\n\n // Gate SPA access on the primary (local API) resource token carrying scopes.\n // Secondary resources are CORS APIs and may legitimately be empty for some users.\n const claims = await this.authService.getAccessTokenClaims(this.primaryResource);\n this.loading.set(false);\n\n if (!(claims.scope ?? '').trim()) {\n this.error.set(this.noAccessMessage);\n return;\n }\n\n const destination = this.historyService.consumeLastVisitedRoute() || '/';\n this.router.navigateByUrl(destination);\n } catch (err: unknown) {\n this.loading.set(false);\n this.error.set(err instanceof Error ? err.message : 'Authentication failed.');\n }\n }\n}\n","<div class=\"callback-container\">\n @if (loading()) {\n <div class=\"loading\">\n <div class=\"spinner\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n <p class=\"loading-text\">Completing sign-in, please wait...</p>\n </div>\n }\n\n @if (!loading() && error()) {\n <div class=\"error\" role=\"alert\">\n {{ error() }}\n </div>\n\n <a class=\"return-link\" routerLink=\"/\">Return to Login</a>\n }\n</div>\n","import { ChangeDetectionStrategy, Component, inject } from '@angular/core';\nimport { AuthService } from '../auth.service';\n\n@Component({\n selector: 'lib-signed-out',\n templateUrl: './signed-out.component.html',\n styleUrl: './signed-out.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class SignedOutComponent {\n private authService = inject(AuthService);\n\n /** Restart the Logto sign-in flow (redirects to the hosted UI). */\n signIn(): void {\n this.authService.signIn();\n }\n}\n","<div class=\"signed-out-container\">\n <div class=\"card\">\n <h2 class=\"title\">Signed out</h2>\n <p class=\"message\">You have been signed out.</p>\n <button type=\"button\" class=\"sign-in-button\" (click)=\"signIn()\">Sign in again</button>\n </div>\n</div>\n","import { Routes } from '@angular/router';\nimport { CallbackComponent } from './callback/callback.component';\nimport { SignedOutComponent } from './signed-out/signed-out.component';\nimport { LogtoRoutingConfig } from './logto.config';\n\n/** Routes accept a path relative to the app root, so drop any leading slash. */\nfunction toRoutePath(path: string): string {\n return path.replace(/^\\//, '');\n}\n\n/**\n * Returns the auth routes (callback + signed-out landing) for the configured paths. Spread the\n * result into the app's top-level `Routes`. Passing the same `routing` config used by\n * `provideLogtoAuth()` keeps the route definitions and the redirect URIs from drifting apart.\n *\n * @example\n * ```ts\n * export const routes: Routes = [\n * {path: '', pathMatch: 'full', redirectTo: '/dashboard'},\n * ...getAuthRoutes(environment.logto.routing),\n * // ...app routes\n * ];\n * ```\n */\nexport function getAuthRoutes(\n routing: Pick<LogtoRoutingConfig, 'callbackPath' | 'signedOutPath'>,\n): Routes {\n return [\n { path: toRoutePath(routing.callbackPath), component: CallbackComponent },\n { path: toRoutePath(routing.signedOutPath), component: SignedOutComponent },\n ];\n}\n","import { inject } from '@angular/core';\nimport { CanActivateFn } from '@angular/router';\nimport { AuthService } from '../auth.service';\nimport { HistoryService } from '../history.service';\n\n/**\n * Replaces `autoLoginPartialRoutesGuard`. Allows activation when a Logto session\n * exists; otherwise records the attempted route and kicks off a sign-in redirect.\n */\nexport const authGuard: CanActivateFn = async (_route, state) => {\n const auth = inject(AuthService);\n const history = inject(HistoryService);\n\n if (await auth.refreshAuthState()) {\n return true;\n }\n\n if (!state.url.startsWith('/auth')) {\n history.setLastVisitedRoute(state.url);\n }\n auth.signIn();\n return false;\n};\n","import { inject } from '@angular/core';\nimport { HttpInterceptorFn } from '@angular/common/http';\nimport { from, switchMap } from 'rxjs';\nimport { AuthService } from '../auth.service';\nimport { SecureRouteMapping } from '../logto.config';\nimport { LOGTO_AUTH_CONFIG } from '../tokens';\n\n/**\n * Picks the configured resource whose `routes` match a request URL, so the interceptor can\n * fetch the correct resource-scoped token. Returns `undefined` when no resource matches (the\n * request goes out without an Authorization header).\n */\nexport function resourceForUrl(\n url: string,\n secureRoutes: SecureRouteMapping[],\n): string | undefined {\n const match = secureRoutes.find((mapping) =>\n mapping.routes.some((route) => url.startsWith(route)),\n );\n\n return match?.resource;\n}\n\n/**\n * Matches the outgoing URL against each configured resource's `routes`; on a match it fetches\n * that resource's access token (cached/refreshed by the Logto client) and attaches it as a\n * Bearer header. Requests that match no resource pass through unauthenticated.\n */\nexport const logtoTokenInterceptor: HttpInterceptorFn = (req, next) => {\n const { routing } = inject(LOGTO_AUTH_CONFIG);\n\n const resource = resourceForUrl(req.url, routing.secureRoutes);\n\n if (!resource) {\n return next(req);\n }\n\n const auth = inject(AuthService);\n\n return from(auth.getAccessToken(resource)).pipe(\n switchMap((token) => {\n if (token) {\n return next(req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }));\n }\n\n return next(req);\n }),\n );\n};\n","import { inject } from '@angular/core';\nimport { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';\nimport { tap } from 'rxjs/operators';\nimport { AuthService } from '../auth.service';\n\n/**\n * If any /api call returns 401, sign the user out and send them to the login page.\n */\nexport const logoutOnUnauthInterceptor: HttpInterceptorFn = (req, next) => {\n const authService = inject(AuthService);\n\n return next(req).pipe(\n tap({\n error: (err) => {\n if (err instanceof HttpErrorResponse && err.status === 401) {\n authService.logout();\n }\n },\n }),\n );\n};\n","import { inject } from '@angular/core';\nimport { Router, RoutesRecognized } from '@angular/router';\nimport { filter } from 'rxjs/operators';\nimport { HistoryService } from '../history.service';\n\n/**\n * Initializer that tracks the last visited route.\n * Excludes auth routes (/auth/*) to prevent redirect loops.\n */\nexport function initializeRouteTracking() {\n return () => {\n const router = inject(Router);\n const historyService = inject(HistoryService);\n\n router.events\n .pipe(filter((event) => event instanceof RoutesRecognized))\n .subscribe((event: RoutesRecognized) => {\n if (!event.urlAfterRedirects.startsWith('/auth')) {\n historyService.setLastVisitedRoute(event.urlAfterRedirects);\n }\n });\n };\n}\n","/*\n * Public API Surface of @ratespecial/logto-angular\n */\n\n// Services\nexport * from './lib/auth.service';\nexport * from './lib/history.service';\n\n// Provider / config\nexport * from './lib/provide-auth';\nexport * from './lib/logto.config';\n\n// Routing\nexport * from './lib/routes';\nexport * from './lib/guards/auth.guard';\n\n// Interceptors\nexport * from './lib/interceptors/logto-token.interceptor';\nexport * from './lib/interceptors/logout-on-unauth.interceptor';\n\n// Initializer\nexport * from './lib/initializers/route-tracking.initializer';\n\n// Tokens / types\nexport * from './lib/tokens';\n\n// Components\nexport * from './lib/callback/callback.component';\nexport * from './lib/signed-out/signed-out.component';\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,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ratespecial/logto-angular",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Angular Logto.io integration using @logto/browser",
|
|
5
|
+
"author": "kpierce@ratespecial.com",
|
|
6
|
+
"homepage": "https://github.com/ratespecial/logto-angular",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/ratespecial/logto-angular.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/ratespecial/logto-angular/issues"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"@angular/common": "^21.2.0",
|
|
17
|
+
"@angular/core": "^21.2.0",
|
|
18
|
+
"@angular/router": "^21.2.0",
|
|
19
|
+
"@logto/browser": "^3.0.13",
|
|
20
|
+
"rxjs": "~7.8.0"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"tslib": "^2.3.0"
|
|
24
|
+
},
|
|
25
|
+
"sideEffects": false,
|
|
26
|
+
"module": "fesm2022/ratespecial-logto-angular.mjs",
|
|
27
|
+
"typings": "types/ratespecial-logto-angular.d.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
"./package.json": {
|
|
30
|
+
"default": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./types/ratespecial-logto-angular.d.ts",
|
|
34
|
+
"default": "./fesm2022/ratespecial-logto-angular.mjs"
|
|
35
|
+
},
|
|
36
|
+
"./testing": {
|
|
37
|
+
"types": "./types/ratespecial-logto-angular-testing.d.ts",
|
|
38
|
+
"default": "./fesm2022/ratespecial-logto-angular-testing.mjs"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"type": "module"
|
|
42
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Provider } from '@angular/core';
|
|
2
|
+
import LogtoClient from '@logto/browser';
|
|
3
|
+
import { LogtoAuthConfig } from '@ratespecial/logto-angular';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Test doubles for the auth layer. Provides a no-op `LogtoClient` (unauthenticated by default)
|
|
7
|
+
* plus the `LOGTO_AUTH_CONFIG` / `PRIMARY_RESOURCE` tokens that `AuthService` and the auth
|
|
8
|
+
* components depend on, so components under test can inject them without a real Logto setup.
|
|
9
|
+
*
|
|
10
|
+
* @param clientOverrides - Partial `LogtoClient` methods to override on the stub.
|
|
11
|
+
* @param configOverrides - Partial `LogtoAuthConfig` fields to override on the default test config.
|
|
12
|
+
*/
|
|
13
|
+
declare function provideLogtoTesting(clientOverrides?: Partial<LogtoClient>, configOverrides?: Partial<LogtoAuthConfig>): Provider[];
|
|
14
|
+
|
|
15
|
+
export { provideLogtoTesting };
|