primus-angular-auth 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/esm2022/lib/primus-auth.guard.mjs +23 -0
- package/esm2022/lib/primus-auth.interceptor.mjs +45 -0
- package/esm2022/lib/primus-auth.provider.mjs +29 -0
- package/esm2022/lib/primus-auth.service.mjs +161 -0
- package/esm2022/lib/primus-auth.tokens.mjs +3 -0
- package/esm2022/lib/primus-auth.types.mjs +2 -0
- package/esm2022/primus-angular-auth.mjs +5 -0
- package/esm2022/public-api.mjs +9 -0
- package/fesm2022/primus-angular-auth.mjs +259 -0
- package/fesm2022/primus-angular-auth.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/lib/primus-auth.guard.d.ts +2 -0
- package/lib/primus-auth.interceptor.d.ts +2 -0
- package/lib/primus-auth.provider.d.ts +3 -0
- package/lib/primus-auth.service.d.ts +49 -0
- package/lib/primus-auth.tokens.d.ts +3 -0
- package/lib/primus-auth.types.d.ts +19 -0
- package/package.json +29 -0
- package/public-api.d.ts +5 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Router } from '@angular/router';
|
|
2
|
+
import { inject } from '@angular/core';
|
|
3
|
+
import { PrimusAuthService } from './primus-auth.service';
|
|
4
|
+
import { map, filter, switchMap, take } from 'rxjs/operators';
|
|
5
|
+
import { PRIMUS_AUTH_CONFIG } from './primus-auth.tokens';
|
|
6
|
+
export const primusAuthGuard = (route, state) => {
|
|
7
|
+
const auth = inject(PrimusAuthService);
|
|
8
|
+
const router = inject(Router);
|
|
9
|
+
const config = inject(PRIMUS_AUTH_CONFIG);
|
|
10
|
+
// Wait for initialization to complete, then check authentication status
|
|
11
|
+
return auth.initialized$.pipe(filter((isInit) => isInit), // Wait until true
|
|
12
|
+
take(1), switchMap(() => auth.isAuthenticated$), take(1), map((isAuthenticated) => {
|
|
13
|
+
if (isAuthenticated) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
// Redirect to login
|
|
17
|
+
const loginPath = config.loginPath || '/login';
|
|
18
|
+
return router.createUrlTree([loginPath], {
|
|
19
|
+
queryParams: { returnUrl: state.url }
|
|
20
|
+
});
|
|
21
|
+
}));
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJpbXVzLWF1dGguZ3VhcmQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9hbmd1bGFyL3ByaW11cy1hdXRoL3NyYy9saWIvcHJpbXVzLWF1dGguZ3VhcmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFpQixNQUFNLEVBQXdELE1BQU0saUJBQWlCLENBQUM7QUFDOUcsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUN2QyxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUMxRCxPQUFPLEVBQUUsR0FBRyxFQUFFLE1BQU0sRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDOUQsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFFMUQsTUFBTSxDQUFDLE1BQU0sZUFBZSxHQUFrQixDQUFDLEtBQTZCLEVBQUUsS0FBMEIsRUFBRSxFQUFFO0lBQ3hHLE1BQU0sSUFBSSxHQUFHLE1BQU0sQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO0lBQ3ZDLE1BQU0sTUFBTSxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUM5QixNQUFNLE1BQU0sR0FBRyxNQUFNLENBQUMsa0JBQWtCLENBQUMsQ0FBQztJQUUxQyx3RUFBd0U7SUFDeEUsT0FBTyxJQUFJLENBQUMsWUFBWSxDQUFDLElBQUksQ0FDekIsTUFBTSxDQUFDLENBQUMsTUFBZSxFQUFFLEVBQUUsQ0FBQyxNQUFNLENBQUMsRUFBRSxrQkFBa0I7SUFDdkQsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUNQLFNBQVMsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsRUFDdEMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUNQLEdBQUcsQ0FBQyxDQUFDLGVBQXdCLEVBQUUsRUFBRTtRQUM3QixJQUFJLGVBQWUsRUFBRTtZQUNqQixPQUFPLElBQUksQ0FBQztTQUNmO1FBRUQsb0JBQW9CO1FBQ3BCLE1BQU0sU0FBUyxHQUFHLE1BQU0sQ0FBQyxTQUFTLElBQUksUUFBUSxDQUFDO1FBQy9DLE9BQU8sTUFBTSxDQUFDLGFBQWEsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxFQUFFO1lBQ3JDLFdBQVcsRUFBRSxFQUFFLFNBQVMsRUFBRSxLQUFLLENBQUMsR0FBRyxFQUFFO1NBQ3hDLENBQUMsQ0FBQztJQUNQLENBQUMsQ0FBQyxDQUNMLENBQUM7QUFDTixDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDYW5BY3RpdmF0ZUZuLCBSb3V0ZXIsIFVybFRyZWUsIEFjdGl2YXRlZFJvdXRlU25hcHNob3QsIFJvdXRlclN0YXRlU25hcHNob3QgfSBmcm9tICdAYW5ndWxhci9yb3V0ZXInO1xyXG5pbXBvcnQgeyBpbmplY3QgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcclxuaW1wb3J0IHsgUHJpbXVzQXV0aFNlcnZpY2UgfSBmcm9tICcuL3ByaW11cy1hdXRoLnNlcnZpY2UnO1xyXG5pbXBvcnQgeyBtYXAsIGZpbHRlciwgc3dpdGNoTWFwLCB0YWtlIH0gZnJvbSAncnhqcy9vcGVyYXRvcnMnO1xyXG5pbXBvcnQgeyBQUklNVVNfQVVUSF9DT05GSUcgfSBmcm9tICcuL3ByaW11cy1hdXRoLnRva2Vucyc7XHJcblxyXG5leHBvcnQgY29uc3QgcHJpbXVzQXV0aEd1YXJkOiBDYW5BY3RpdmF0ZUZuID0gKHJvdXRlOiBBY3RpdmF0ZWRSb3V0ZVNuYXBzaG90LCBzdGF0ZTogUm91dGVyU3RhdGVTbmFwc2hvdCkgPT4ge1xyXG4gICAgY29uc3QgYXV0aCA9IGluamVjdChQcmltdXNBdXRoU2VydmljZSk7XHJcbiAgICBjb25zdCByb3V0ZXIgPSBpbmplY3QoUm91dGVyKTtcclxuICAgIGNvbnN0IGNvbmZpZyA9IGluamVjdChQUklNVVNfQVVUSF9DT05GSUcpO1xyXG5cclxuICAgIC8vIFdhaXQgZm9yIGluaXRpYWxpemF0aW9uIHRvIGNvbXBsZXRlLCB0aGVuIGNoZWNrIGF1dGhlbnRpY2F0aW9uIHN0YXR1c1xyXG4gICAgcmV0dXJuIGF1dGguaW5pdGlhbGl6ZWQkLnBpcGUoXHJcbiAgICAgICAgZmlsdGVyKChpc0luaXQ6IGJvb2xlYW4pID0+IGlzSW5pdCksIC8vIFdhaXQgdW50aWwgdHJ1ZVxyXG4gICAgICAgIHRha2UoMSksXHJcbiAgICAgICAgc3dpdGNoTWFwKCgpID0+IGF1dGguaXNBdXRoZW50aWNhdGVkJCksXHJcbiAgICAgICAgdGFrZSgxKSxcclxuICAgICAgICBtYXAoKGlzQXV0aGVudGljYXRlZDogYm9vbGVhbikgPT4ge1xyXG4gICAgICAgICAgICBpZiAoaXNBdXRoZW50aWNhdGVkKSB7XHJcbiAgICAgICAgICAgICAgICByZXR1cm4gdHJ1ZTtcclxuICAgICAgICAgICAgfVxyXG5cclxuICAgICAgICAgICAgLy8gUmVkaXJlY3QgdG8gbG9naW5cclxuICAgICAgICAgICAgY29uc3QgbG9naW5QYXRoID0gY29uZmlnLmxvZ2luUGF0aCB8fCAnL2xvZ2luJztcclxuICAgICAgICAgICAgcmV0dXJuIHJvdXRlci5jcmVhdGVVcmxUcmVlKFtsb2dpblBhdGhdLCB7XHJcbiAgICAgICAgICAgICAgICBxdWVyeVBhcmFtczogeyByZXR1cm5Vcmw6IHN0YXRlLnVybCB9XHJcbiAgICAgICAgICAgIH0pO1xyXG4gICAgICAgIH0pXHJcbiAgICApO1xyXG59O1xyXG4iXX0=
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { inject } from '@angular/core';
|
|
2
|
+
import { catchError, throwError } from 'rxjs';
|
|
3
|
+
import { PrimusAuthService } from './primus-auth.service';
|
|
4
|
+
export const primusAuthInterceptor = (req, next) => {
|
|
5
|
+
const authService = inject(PrimusAuthService);
|
|
6
|
+
// 1. Add CSRF Header to non-GET requests
|
|
7
|
+
// Double-Submit Cookie Pattern: Read XSRF-TOKEN cookie and mirror in header.
|
|
8
|
+
let request = req;
|
|
9
|
+
if (req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'OPTIONS') {
|
|
10
|
+
const token = getCookie('XSRF-TOKEN') || '';
|
|
11
|
+
request = req.clone({
|
|
12
|
+
setHeaders: {
|
|
13
|
+
'X-Primus-CSRF': token
|
|
14
|
+
},
|
|
15
|
+
withCredentials: true // Ensure cookies are sent
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
// Determine if we need credentials for GETs?
|
|
20
|
+
// Usually yes for API calls to our backend.
|
|
21
|
+
// We assume calls to same origin or API URL need creds.
|
|
22
|
+
request = req.clone({
|
|
23
|
+
withCredentials: true
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return next(request).pipe(catchError((error) => {
|
|
27
|
+
// 2. Handle 401 Unauthorized
|
|
28
|
+
if (error.status === 401) {
|
|
29
|
+
authService.handleUnauthorized();
|
|
30
|
+
}
|
|
31
|
+
return throwError(() => error);
|
|
32
|
+
}));
|
|
33
|
+
};
|
|
34
|
+
function getCookie(name) {
|
|
35
|
+
try {
|
|
36
|
+
if (typeof document === 'undefined')
|
|
37
|
+
return null;
|
|
38
|
+
const matches = document.cookie.match(new RegExp('(?:^|; )' + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + '=([^;]*)'));
|
|
39
|
+
return matches ? decodeURIComponent(matches[1]) : null;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJpbXVzLWF1dGguaW50ZXJjZXB0b3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9hbmd1bGFyL3ByaW11cy1hdXRoL3NyYy9saWIvcHJpbXVzLWF1dGguaW50ZXJjZXB0b3IudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQ0EsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUN2QyxPQUFPLEVBQUUsVUFBVSxFQUFFLFVBQVUsRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUM5QyxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUUxRCxNQUFNLENBQUMsTUFBTSxxQkFBcUIsR0FBc0IsQ0FBQyxHQUFHLEVBQUUsSUFBSSxFQUFFLEVBQUU7SUFDbEUsTUFBTSxXQUFXLEdBQUcsTUFBTSxDQUFDLGlCQUFpQixDQUFDLENBQUM7SUFFMUMseUNBQXlDO0lBQ3pDLDZFQUE2RTtJQUM3RSxJQUFJLE9BQU8sR0FBRyxHQUFHLENBQUM7SUFDbEIsSUFBSSxHQUFHLENBQUMsTUFBTSxLQUFLLEtBQUssSUFBSSxHQUFHLENBQUMsTUFBTSxLQUFLLE1BQU0sSUFBSSxHQUFHLENBQUMsTUFBTSxLQUFLLFNBQVMsRUFBRTtRQUMzRSxNQUFNLEtBQUssR0FBRyxTQUFTLENBQUMsWUFBWSxDQUFDLElBQUksRUFBRSxDQUFDO1FBQzVDLE9BQU8sR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDO1lBQ2hCLFVBQVUsRUFBRTtnQkFDUixlQUFlLEVBQUUsS0FBSzthQUN6QjtZQUNELGVBQWUsRUFBRSxJQUFJLENBQUMsMEJBQTBCO1NBQ25ELENBQUMsQ0FBQztLQUNOO1NBQU07UUFDSCw2Q0FBNkM7UUFDN0MsNENBQTRDO1FBQzVDLHdEQUF3RDtRQUN4RCxPQUFPLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQztZQUNoQixlQUFlLEVBQUUsSUFBSTtTQUN4QixDQUFDLENBQUM7S0FDTjtJQUVELE9BQU8sSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDLElBQUksQ0FDckIsVUFBVSxDQUFDLENBQUMsS0FBd0IsRUFBRSxFQUFFO1FBQ3BDLDZCQUE2QjtRQUM3QixJQUFJLEtBQUssQ0FBQyxNQUFNLEtBQUssR0FBRyxFQUFFO1lBQ3RCLFdBQVcsQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO1NBQ3BDO1FBQ0QsT0FBTyxVQUFVLENBQUMsR0FBRyxFQUFFLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDbkMsQ0FBQyxDQUFDLENBQ0wsQ0FBQztBQUNOLENBQUMsQ0FBQztBQUVGLFNBQVMsU0FBUyxDQUFDLElBQVk7SUFDM0IsSUFBSTtRQUNBLElBQUksT0FBTyxRQUFRLEtBQUssV0FBVztZQUFFLE9BQU8sSUFBSSxDQUFDO1FBQ2pELE1BQU0sT0FBTyxHQUFHLFFBQVEsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLElBQUksTUFBTSxDQUFDLFVBQVUsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLDhCQUE4QixFQUFFLE1BQU0sQ0FBQyxHQUFHLFVBQVUsQ0FBQyxDQUFDLENBQUM7UUFDbEksT0FBTyxPQUFPLENBQUMsQ0FBQyxDQUFDLGtCQUFrQixDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7S0FDMUQ7SUFBQyxNQUFNO1FBQ0osT0FBTyxJQUFJLENBQUM7S0FDZjtBQUNMLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBIdHRwSW50ZXJjZXB0b3JGbiwgSHR0cEVycm9yUmVzcG9uc2UgfSBmcm9tICdAYW5ndWxhci9jb21tb24vaHR0cCc7XHJcbmltcG9ydCB7IGluamVjdCB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xyXG5pbXBvcnQgeyBjYXRjaEVycm9yLCB0aHJvd0Vycm9yIH0gZnJvbSAncnhqcyc7XHJcbmltcG9ydCB7IFByaW11c0F1dGhTZXJ2aWNlIH0gZnJvbSAnLi9wcmltdXMtYXV0aC5zZXJ2aWNlJztcclxuXHJcbmV4cG9ydCBjb25zdCBwcmltdXNBdXRoSW50ZXJjZXB0b3I6IEh0dHBJbnRlcmNlcHRvckZuID0gKHJlcSwgbmV4dCkgPT4ge1xyXG4gICAgY29uc3QgYXV0aFNlcnZpY2UgPSBpbmplY3QoUHJpbXVzQXV0aFNlcnZpY2UpO1xyXG5cclxuICAgICAgICAvLyAxLiBBZGQgQ1NSRiBIZWFkZXIgdG8gbm9uLUdFVCByZXF1ZXN0c1xyXG4gICAgICAgIC8vIERvdWJsZS1TdWJtaXQgQ29va2llIFBhdHRlcm46IFJlYWQgWFNSRi1UT0tFTiBjb29raWUgYW5kIG1pcnJvciBpbiBoZWFkZXIuXHJcbiAgICAgICAgbGV0IHJlcXVlc3QgPSByZXE7XHJcbiAgICAgICAgaWYgKHJlcS5tZXRob2QgIT09ICdHRVQnICYmIHJlcS5tZXRob2QgIT09ICdIRUFEJyAmJiByZXEubWV0aG9kICE9PSAnT1BUSU9OUycpIHtcclxuICAgICAgICAgICAgY29uc3QgdG9rZW4gPSBnZXRDb29raWUoJ1hTUkYtVE9LRU4nKSB8fCAnJztcclxuICAgICAgICAgICAgcmVxdWVzdCA9IHJlcS5jbG9uZSh7XHJcbiAgICAgICAgICAgICAgICBzZXRIZWFkZXJzOiB7XHJcbiAgICAgICAgICAgICAgICAgICAgJ1gtUHJpbXVzLUNTUkYnOiB0b2tlblxyXG4gICAgICAgICAgICAgICAgfSxcclxuICAgICAgICAgICAgICAgIHdpdGhDcmVkZW50aWFsczogdHJ1ZSAvLyBFbnN1cmUgY29va2llcyBhcmUgc2VudFxyXG4gICAgICAgICAgICB9KTtcclxuICAgICAgICB9IGVsc2Uge1xyXG4gICAgICAgICAgICAvLyBEZXRlcm1pbmUgaWYgd2UgbmVlZCBjcmVkZW50aWFscyBmb3IgR0VUcz9cclxuICAgICAgICAgICAgLy8gVXN1YWxseSB5ZXMgZm9yIEFQSSBjYWxscyB0byBvdXIgYmFja2VuZC5cclxuICAgICAgICAgICAgLy8gV2UgYXNzdW1lIGNhbGxzIHRvIHNhbWUgb3JpZ2luIG9yIEFQSSBVUkwgbmVlZCBjcmVkcy5cclxuICAgICAgICAgICAgcmVxdWVzdCA9IHJlcS5jbG9uZSh7XHJcbiAgICAgICAgICAgICAgICB3aXRoQ3JlZGVudGlhbHM6IHRydWVcclxuICAgICAgICAgICAgfSk7XHJcbiAgICAgICAgfVxyXG4gICAgXHJcbiAgICAgICAgcmV0dXJuIG5leHQocmVxdWVzdCkucGlwZShcclxuICAgICAgICAgICAgY2F0Y2hFcnJvcigoZXJyb3I6IEh0dHBFcnJvclJlc3BvbnNlKSA9PiB7XHJcbiAgICAgICAgICAgICAgICAvLyAyLiBIYW5kbGUgNDAxIFVuYXV0aG9yaXplZFxyXG4gICAgICAgICAgICAgICAgaWYgKGVycm9yLnN0YXR1cyA9PT0gNDAxKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgYXV0aFNlcnZpY2UuaGFuZGxlVW5hdXRob3JpemVkKCk7XHJcbiAgICAgICAgICAgICAgICB9XHJcbiAgICAgICAgICAgICAgICByZXR1cm4gdGhyb3dFcnJvcigoKSA9PiBlcnJvcik7XHJcbiAgICAgICAgICAgIH0pXHJcbiAgICAgICAgKTtcclxuICAgIH07XHJcbiAgICBcclxuICAgIGZ1bmN0aW9uIGdldENvb2tpZShuYW1lOiBzdHJpbmcpOiBzdHJpbmcgfCBudWxsIHtcclxuICAgICAgICB0cnkge1xyXG4gICAgICAgICAgICBpZiAodHlwZW9mIGRvY3VtZW50ID09PSAndW5kZWZpbmVkJykgcmV0dXJuIG51bGw7XHJcbiAgICAgICAgICAgIGNvbnN0IG1hdGNoZXMgPSBkb2N1bWVudC5jb29raWUubWF0Y2gobmV3IFJlZ0V4cCgnKD86Xnw7ICknICsgbmFtZS5yZXBsYWNlKC8oW1xcLiQ/Knx7fVxcKFxcKVxcW1xcXVxcXFxcXC9cXCteXSkvZywgJ1xcXFwkMScpICsgJz0oW147XSopJykpO1xyXG4gICAgICAgICAgICByZXR1cm4gbWF0Y2hlcyA/IGRlY29kZVVSSUNvbXBvbmVudChtYXRjaGVzWzFdKSA6IG51bGw7XHJcbiAgICAgICAgfSBjYXRjaCB7XHJcbiAgICAgICAgICAgIHJldHVybiBudWxsO1xyXG4gICAgICAgIH1cclxuICAgIH1cclxuIl19
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { makeEnvironmentProviders, APP_INITIALIZER, PLATFORM_ID } from '@angular/core';
|
|
2
|
+
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
|
3
|
+
import { PRIMUS_AUTH_CONFIG } from './primus-auth.tokens';
|
|
4
|
+
import { PrimusAuthService } from './primus-auth.service';
|
|
5
|
+
import { primusAuthInterceptor } from './primus-auth.interceptor';
|
|
6
|
+
export function providePrimusAuth(config) {
|
|
7
|
+
return makeEnvironmentProviders([
|
|
8
|
+
{
|
|
9
|
+
provide: PRIMUS_AUTH_CONFIG,
|
|
10
|
+
useValue: config
|
|
11
|
+
},
|
|
12
|
+
PrimusAuthService,
|
|
13
|
+
provideHttpClient(withInterceptors([primusAuthInterceptor])),
|
|
14
|
+
{
|
|
15
|
+
provide: APP_INITIALIZER,
|
|
16
|
+
useFactory: (auth, platform) => () => {
|
|
17
|
+
// We trigger checkSession in constructor, but we can also do it here if we want to await it.
|
|
18
|
+
// However, awaiting APP_INITIALIZER blocks the whole app render.
|
|
19
|
+
// Better to let it run in background and use the Guard to block specific routes.
|
|
20
|
+
// So we just ensure service is instantiated.
|
|
21
|
+
// Actually, checkSession is already called in constructor.
|
|
22
|
+
// This APP_INITIALIZER forces the service to be created immediately on app start.
|
|
23
|
+
},
|
|
24
|
+
deps: [PrimusAuthService, PLATFORM_ID],
|
|
25
|
+
multi: true
|
|
26
|
+
}
|
|
27
|
+
]);
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJpbXVzLWF1dGgucHJvdmlkZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9hbmd1bGFyL3ByaW11cy1hdXRoL3NyYy9saWIvcHJpbXVzLWF1dGgucHJvdmlkZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUF3Qix3QkFBd0IsRUFBWSxlQUFlLEVBQUUsV0FBVyxFQUFVLE1BQU0sZUFBZSxDQUFDO0FBQy9ILE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBRTNFLE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQzFELE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBQzFELE9BQU8sRUFBRSxxQkFBcUIsRUFBRSxNQUFNLDJCQUEyQixDQUFDO0FBR2xFLE1BQU0sVUFBVSxpQkFBaUIsQ0FBQyxNQUF3QjtJQUN0RCxPQUFPLHdCQUF3QixDQUFDO1FBQzVCO1lBQ0ksT0FBTyxFQUFFLGtCQUFrQjtZQUMzQixRQUFRLEVBQUUsTUFBTTtTQUNuQjtRQUNELGlCQUFpQjtRQUNqQixpQkFBaUIsQ0FDYixnQkFBZ0IsQ0FBQyxDQUFDLHFCQUFxQixDQUFDLENBQUMsQ0FDNUM7UUFDRDtZQUNJLE9BQU8sRUFBRSxlQUFlO1lBQ3hCLFVBQVUsRUFBRSxDQUFDLElBQXVCLEVBQUUsUUFBZ0IsRUFBRSxFQUFFLENBQUMsR0FBRyxFQUFFO2dCQUM1RCw2RkFBNkY7Z0JBQzdGLGlFQUFpRTtnQkFDakUsaUZBQWlGO2dCQUNqRiw2Q0FBNkM7Z0JBRTdDLDJEQUEyRDtnQkFDM0Qsa0ZBQWtGO1lBQ3RGLENBQUM7WUFDRCxJQUFJLEVBQUUsQ0FBQyxpQkFBaUIsRUFBRSxXQUFXLENBQUM7WUFDdEMsS0FBSyxFQUFFLElBQUk7U0FDZDtLQUNKLENBQUMsQ0FBQztBQUNQLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBFbnZpcm9ubWVudFByb3ZpZGVycywgbWFrZUVudmlyb25tZW50UHJvdmlkZXJzLCBQcm92aWRlciwgQVBQX0lOSVRJQUxJWkVSLCBQTEFURk9STV9JRCwgaW5qZWN0IH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XHJcbmltcG9ydCB7IHByb3ZpZGVIdHRwQ2xpZW50LCB3aXRoSW50ZXJjZXB0b3JzIH0gZnJvbSAnQGFuZ3VsYXIvY29tbW9uL2h0dHAnO1xyXG5pbXBvcnQgeyBQcmltdXNBdXRoQ29uZmlnIH0gZnJvbSAnLi9wcmltdXMtYXV0aC50eXBlcyc7XHJcbmltcG9ydCB7IFBSSU1VU19BVVRIX0NPTkZJRyB9IGZyb20gJy4vcHJpbXVzLWF1dGgudG9rZW5zJztcclxuaW1wb3J0IHsgUHJpbXVzQXV0aFNlcnZpY2UgfSBmcm9tICcuL3ByaW11cy1hdXRoLnNlcnZpY2UnO1xyXG5pbXBvcnQgeyBwcmltdXNBdXRoSW50ZXJjZXB0b3IgfSBmcm9tICcuL3ByaW11cy1hdXRoLmludGVyY2VwdG9yJztcclxuaW1wb3J0IHsgaXNQbGF0Zm9ybUJyb3dzZXIgfSBmcm9tICdAYW5ndWxhci9jb21tb24nO1xyXG5cclxuZXhwb3J0IGZ1bmN0aW9uIHByb3ZpZGVQcmltdXNBdXRoKGNvbmZpZzogUHJpbXVzQXV0aENvbmZpZyk6IEVudmlyb25tZW50UHJvdmlkZXJzIHtcclxuICAgIHJldHVybiBtYWtlRW52aXJvbm1lbnRQcm92aWRlcnMoW1xyXG4gICAgICAgIHtcclxuICAgICAgICAgICAgcHJvdmlkZTogUFJJTVVTX0FVVEhfQ09ORklHLFxyXG4gICAgICAgICAgICB1c2VWYWx1ZTogY29uZmlnXHJcbiAgICAgICAgfSxcclxuICAgICAgICBQcmltdXNBdXRoU2VydmljZSxcclxuICAgICAgICBwcm92aWRlSHR0cENsaWVudChcclxuICAgICAgICAgICAgd2l0aEludGVyY2VwdG9ycyhbcHJpbXVzQXV0aEludGVyY2VwdG9yXSlcclxuICAgICAgICApLFxyXG4gICAgICAgIHtcclxuICAgICAgICAgICAgcHJvdmlkZTogQVBQX0lOSVRJQUxJWkVSLFxyXG4gICAgICAgICAgICB1c2VGYWN0b3J5OiAoYXV0aDogUHJpbXVzQXV0aFNlcnZpY2UsIHBsYXRmb3JtOiBPYmplY3QpID0+ICgpID0+IHtcclxuICAgICAgICAgICAgICAgIC8vIFdlIHRyaWdnZXIgY2hlY2tTZXNzaW9uIGluIGNvbnN0cnVjdG9yLCBidXQgd2UgY2FuIGFsc28gZG8gaXQgaGVyZSBpZiB3ZSB3YW50IHRvIGF3YWl0IGl0LlxyXG4gICAgICAgICAgICAgICAgLy8gSG93ZXZlciwgYXdhaXRpbmcgQVBQX0lOSVRJQUxJWkVSIGJsb2NrcyB0aGUgd2hvbGUgYXBwIHJlbmRlci5cclxuICAgICAgICAgICAgICAgIC8vIEJldHRlciB0byBsZXQgaXQgcnVuIGluIGJhY2tncm91bmQgYW5kIHVzZSB0aGUgR3VhcmQgdG8gYmxvY2sgc3BlY2lmaWMgcm91dGVzLlxyXG4gICAgICAgICAgICAgICAgLy8gU28gd2UganVzdCBlbnN1cmUgc2VydmljZSBpcyBpbnN0YW50aWF0ZWQuXHJcblxyXG4gICAgICAgICAgICAgICAgLy8gQWN0dWFsbHksIGNoZWNrU2Vzc2lvbiBpcyBhbHJlYWR5IGNhbGxlZCBpbiBjb25zdHJ1Y3Rvci5cclxuICAgICAgICAgICAgICAgIC8vIFRoaXMgQVBQX0lOSVRJQUxJWkVSIGZvcmNlcyB0aGUgc2VydmljZSB0byBiZSBjcmVhdGVkIGltbWVkaWF0ZWx5IG9uIGFwcCBzdGFydC5cclxuICAgICAgICAgICAgfSxcclxuICAgICAgICAgICAgZGVwczogW1ByaW11c0F1dGhTZXJ2aWNlLCBQTEFURk9STV9JRF0sXHJcbiAgICAgICAgICAgIG11bHRpOiB0cnVlXHJcbiAgICAgICAgfVxyXG4gICAgXSk7XHJcbn1cclxuIl19
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { Injectable, Inject, signal, computed, PLATFORM_ID } from '@angular/core';
|
|
2
|
+
import { isPlatformBrowser } from '@angular/common';
|
|
3
|
+
import { catchError, map, tap } from 'rxjs/operators';
|
|
4
|
+
import { of, BehaviorSubject, interval } from 'rxjs';
|
|
5
|
+
import { PRIMUS_AUTH_CONFIG } from './primus-auth.tokens';
|
|
6
|
+
import * as i0 from "@angular/core";
|
|
7
|
+
import * as i1 from "@angular/common/http";
|
|
8
|
+
import * as i2 from "@angular/router";
|
|
9
|
+
import * as i3 from "@angular/common";
|
|
10
|
+
export class PrimusAuthService {
|
|
11
|
+
http;
|
|
12
|
+
router;
|
|
13
|
+
location;
|
|
14
|
+
config;
|
|
15
|
+
platformId;
|
|
16
|
+
// Reactive State (Signals)
|
|
17
|
+
_user = signal(null);
|
|
18
|
+
_isLoading = signal(true);
|
|
19
|
+
user = this._user.asReadonly();
|
|
20
|
+
isLoading = this._isLoading.asReadonly();
|
|
21
|
+
isAuthenticated = computed(() => !!this._user());
|
|
22
|
+
// Legacy Observable support
|
|
23
|
+
user$ = new BehaviorSubject(null);
|
|
24
|
+
isAuthenticated$ = this.user$.pipe(map(u => !!u));
|
|
25
|
+
initialized$ = new BehaviorSubject(false);
|
|
26
|
+
heartbeatSub = null;
|
|
27
|
+
HEARTBEAT_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
|
28
|
+
constructor(http, router, location, config, platformId) {
|
|
29
|
+
this.http = http;
|
|
30
|
+
this.router = router;
|
|
31
|
+
this.location = location;
|
|
32
|
+
this.config = config;
|
|
33
|
+
this.platformId = platformId;
|
|
34
|
+
this.checkSession();
|
|
35
|
+
}
|
|
36
|
+
ngOnDestroy() {
|
|
37
|
+
this.stopHeartbeat();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Checks /api/auth/me to see if user is logged in.
|
|
41
|
+
* Should be called on app startup.
|
|
42
|
+
*/
|
|
43
|
+
checkSession() {
|
|
44
|
+
if (!isPlatformBrowser(this.platformId)) {
|
|
45
|
+
this._isLoading.set(false);
|
|
46
|
+
this.initialized$.next(true);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this._isLoading.set(true);
|
|
50
|
+
this.fetchUser().subscribe(() => {
|
|
51
|
+
this._isLoading.set(false);
|
|
52
|
+
this.initialized$.next(true);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
fetchUser() {
|
|
56
|
+
const endpoint = `${this.config.apiUrl}/api/auth/me`;
|
|
57
|
+
return this.http.get(endpoint, { withCredentials: true }).pipe(tap(user => this.updateState(user)), catchError(() => {
|
|
58
|
+
this.updateState(null);
|
|
59
|
+
return of(null);
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Performs a local login (username/password) via the Backend Broker.
|
|
64
|
+
* The backend must accept a POST to /api/auth/login and set a cookie.
|
|
65
|
+
*/
|
|
66
|
+
login(credentials) {
|
|
67
|
+
const endpoint = `${this.config.apiUrl}/api/auth/login`;
|
|
68
|
+
// We expect the backend to return the User object on success (or we fetch it)
|
|
69
|
+
// If the backend returns just 200 OK, we might need to chain a fetchUser()
|
|
70
|
+
return this.http.post(endpoint, credentials, { withCredentials: true }).pipe(tap(user => {
|
|
71
|
+
// If the backend returns the user directly, great.
|
|
72
|
+
// If it returns a generic success, we might get 'null' here if typing doesn't match.
|
|
73
|
+
// For safety, if we get a success response, we can also force-fetch the user to be sure.
|
|
74
|
+
if (user && user.email) {
|
|
75
|
+
this.updateState(user);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// Fallback: Fetch user info if response was just { success: true }
|
|
79
|
+
this.fetchUser().subscribe();
|
|
80
|
+
}
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Redirects the browser to the backend login endpoint for the provider.
|
|
85
|
+
* e.g. /api/auth/azure, /api/auth/auth0
|
|
86
|
+
*/
|
|
87
|
+
loginWithProvider(provider, redirectUrl) {
|
|
88
|
+
const returnUrl = redirectUrl || this.router.url;
|
|
89
|
+
// We can pass returnUrl if backend supports it, usually configured globally
|
|
90
|
+
// or via a query param handled by the Resolution logic.
|
|
91
|
+
// For now we assume standard flow or basic redirect.
|
|
92
|
+
const target = `${this.config.apiUrl}/api/auth/${provider}`;
|
|
93
|
+
window.location.href = target;
|
|
94
|
+
}
|
|
95
|
+
logout() {
|
|
96
|
+
const endpoint = `${this.config.apiUrl}/api/auth/logout`;
|
|
97
|
+
this.http.post(endpoint, {}, {
|
|
98
|
+
withCredentials: true
|
|
99
|
+
// Interceptor will add CSRF header automatically
|
|
100
|
+
}).subscribe({
|
|
101
|
+
next: () => {
|
|
102
|
+
this.updateState(null);
|
|
103
|
+
const redirect = this.config.logoutRedirect || '/login';
|
|
104
|
+
this.router.navigateByUrl(redirect);
|
|
105
|
+
},
|
|
106
|
+
error: () => {
|
|
107
|
+
// Force logout state even if API failed
|
|
108
|
+
this.updateState(null);
|
|
109
|
+
const redirect = this.config.logoutRedirect || '/login';
|
|
110
|
+
this.router.navigateByUrl(redirect);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
updateState(user) {
|
|
115
|
+
this._user.set(user);
|
|
116
|
+
this.user$.next(user);
|
|
117
|
+
// Auto-sync signal to loading state
|
|
118
|
+
if (user) {
|
|
119
|
+
this._isLoading.set(false);
|
|
120
|
+
this.startHeartbeat();
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
this.stopHeartbeat();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Called by Interceptor on 401
|
|
127
|
+
handleUnauthorized() {
|
|
128
|
+
this.updateState(null);
|
|
129
|
+
}
|
|
130
|
+
startHeartbeat() {
|
|
131
|
+
if (!isPlatformBrowser(this.platformId))
|
|
132
|
+
return;
|
|
133
|
+
if (this.heartbeatSub)
|
|
134
|
+
return; // Already running
|
|
135
|
+
this.heartbeatSub = interval(this.HEARTBEAT_INTERVAL).subscribe(() => {
|
|
136
|
+
// Quietly check session
|
|
137
|
+
this.fetchUser().subscribe();
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
stopHeartbeat() {
|
|
141
|
+
if (this.heartbeatSub) {
|
|
142
|
+
this.heartbeatSub.unsubscribe();
|
|
143
|
+
this.heartbeatSub = null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PrimusAuthService, deps: [{ token: i1.HttpClient }, { token: i2.Router }, { token: i3.Location }, { token: PRIMUS_AUTH_CONFIG }, { token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
147
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PrimusAuthService, providedIn: 'root' });
|
|
148
|
+
}
|
|
149
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PrimusAuthService, decorators: [{
|
|
150
|
+
type: Injectable,
|
|
151
|
+
args: [{
|
|
152
|
+
providedIn: 'root'
|
|
153
|
+
}]
|
|
154
|
+
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: i2.Router }, { type: i3.Location }, { type: undefined, decorators: [{
|
|
155
|
+
type: Inject,
|
|
156
|
+
args: [PRIMUS_AUTH_CONFIG]
|
|
157
|
+
}] }, { type: Object, decorators: [{
|
|
158
|
+
type: Inject,
|
|
159
|
+
args: [PLATFORM_ID]
|
|
160
|
+
}] }] });
|
|
161
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJpbXVzLWF1dGguc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL2FuZ3VsYXIvcHJpbXVzLWF1dGgvc3JjL2xpYi9wcmltdXMtYXV0aC5zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxRQUFRLEVBQUUsV0FBVyxFQUFhLE1BQU0sZUFBZSxDQUFDO0FBRzdGLE9BQU8sRUFBRSxpQkFBaUIsRUFBWSxNQUFNLGlCQUFpQixDQUFDO0FBQzlELE9BQU8sRUFBRSxVQUFVLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQ3RELE9BQU8sRUFBYyxFQUFFLEVBQUUsZUFBZSxFQUFnQixRQUFRLEVBQUUsTUFBTSxNQUFNLENBQUM7QUFFL0UsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sc0JBQXNCLENBQUM7Ozs7O0FBSzFELE1BQU0sT0FBTyxpQkFBaUI7SUFrQmQ7SUFDQTtJQUNBO0lBQzRCO0lBQ1A7SUFyQmpDLDJCQUEyQjtJQUNuQixLQUFLLEdBQUcsTUFBTSxDQUFvQixJQUFJLENBQUMsQ0FBQztJQUN4QyxVQUFVLEdBQUcsTUFBTSxDQUFVLElBQUksQ0FBQyxDQUFDO0lBRTNCLElBQUksR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLFVBQVUsRUFBRSxDQUFDO0lBQy9CLFNBQVMsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLFVBQVUsRUFBRSxDQUFDO0lBQ3pDLGVBQWUsR0FBRyxRQUFRLENBQUMsR0FBRyxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQyxDQUFDO0lBRWpFLDRCQUE0QjtJQUNaLEtBQUssR0FBRyxJQUFJLGVBQWUsQ0FBb0IsSUFBSSxDQUFDLENBQUM7SUFDckQsZ0JBQWdCLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsR0FB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QyxXQUFXIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSW5qZWN0YWJsZSwgSW5qZWN0LCBzaWduYWwsIGNvbXB1dGVkLCBQTEFURk9STV9JRCwgT25EZXN0cm95IH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XHJcbmltcG9ydCB7IEh0dHBDbGllbnQgfSBmcm9tICdAYW5ndWxhci9jb21tb24vaHR0cCc7XHJcbmltcG9ydCB7IFJvdXRlciB9IGZyb20gJ0Bhbmd1bGFyL3JvdXRlcic7XHJcbmltcG9ydCB7IGlzUGxhdGZvcm1Ccm93c2VyLCBMb2NhdGlvbiB9IGZyb20gJ0Bhbmd1bGFyL2NvbW1vbic7XHJcbmltcG9ydCB7IGNhdGNoRXJyb3IsIG1hcCwgdGFwIH0gZnJvbSAncnhqcy9vcGVyYXRvcnMnO1xyXG5pbXBvcnQgeyBPYnNlcnZhYmxlLCBvZiwgQmVoYXZpb3JTdWJqZWN0LCBTdWJzY3JpcHRpb24sIGludGVydmFsIH0gZnJvbSAncnhqcyc7XHJcbmltcG9ydCB7IFByaW11c0F1dGhDb25maWcsIFByaW11c1VzZXIgfSBmcm9tICcuL3ByaW11cy1hdXRoLnR5cGVzJztcclxuaW1wb3J0IHsgUFJJTVVTX0FVVEhfQ09ORklHIH0gZnJvbSAnLi9wcmltdXMtYXV0aC50b2tlbnMnO1xyXG5cclxuQEluamVjdGFibGUoe1xyXG4gICAgcHJvdmlkZWRJbjogJ3Jvb3QnXHJcbn0pXHJcbmV4cG9ydCBjbGFzcyBQcmltdXNBdXRoU2VydmljZSBpbXBsZW1lbnRzIE9uRGVzdHJveSB7XHJcbiAgICAvLyBSZWFjdGl2ZSBTdGF0ZSAoU2lnbmFscylcclxuICAgIHByaXZhdGUgX3VzZXIgPSBzaWduYWw8UHJpbXVzVXNlciB8IG51bGw+KG51bGwpO1xyXG4gICAgcHJpdmF0ZSBfaXNMb2FkaW5nID0gc2lnbmFsPGJvb2xlYW4+KHRydWUpO1xyXG5cclxuICAgIHB1YmxpYyByZWFkb25seSB1c2VyID0gdGhpcy5fdXNlci5hc1JlYWRvbmx5KCk7XHJcbiAgICBwdWJsaWMgcmVhZG9ubHkgaXNMb2FkaW5nID0gdGhpcy5faXNMb2FkaW5nLmFzUmVhZG9ubHkoKTtcclxuICAgIHB1YmxpYyByZWFkb25seSBpc0F1dGhlbnRpY2F0ZWQgPSBjb21wdXRlZCgoKSA9PiAhIXRoaXMuX3VzZXIoKSk7XHJcblxyXG4gICAgLy8gTGVnYWN5IE9ic2VydmFibGUgc3VwcG9ydFxyXG4gICAgcHVibGljIHJlYWRvbmx5IHVzZXIkID0gbmV3IEJlaGF2aW9yU3ViamVjdDxQcmltdXNVc2VyIHwgbnVsbD4obnVsbCk7XHJcbiAgICBwdWJsaWMgcmVhZG9ubHkgaXNBdXRoZW50aWNhdGVkJCA9IHRoaXMudXNlciQucGlwZShtYXAodSA9PiAhIXUpKTtcclxuICAgIHB1YmxpYyByZWFkb25seSBpbml0aWFsaXplZCQgPSBuZXcgQmVoYXZpb3JTdWJqZWN0PGJvb2xlYW4+KGZhbHNlKTtcclxuXHJcbiAgICBwcml2YXRlIGhlYXJ0YmVhdFN1YjogU3Vic2NyaXB0aW9uIHwgbnVsbCA9IG51bGw7XHJcbiAgICBwcml2YXRlIHJlYWRvbmx5IEhFQVJUQkVBVF9JTlRFUlZBTCA9IDUgKiA2MCAqIDEwMDA7IC8vIDUgbWludXRlc1xyXG5cclxuICAgIGNvbnN0cnVjdG9yKFxyXG4gICAgICAgIHByaXZhdGUgaHR0cDogSHR0cENsaWVudCxcclxuICAgICAgICBwcml2YXRlIHJvdXRlcjogUm91dGVyLFxyXG4gICAgICAgIHByaXZhdGUgbG9jYXRpb246IExvY2F0aW9uLFxyXG4gICAgICAgIEBJbmplY3QoUFJJTVVTX0FVVEhfQ09ORklHKSBwcml2YXRlIGNvbmZpZzogUHJpbXVzQXV0aENvbmZpZyxcclxuICAgICAgICBASW5qZWN0KFBMQVRGT1JNX0lEKSBwcml2YXRlIHBsYXRmb3JtSWQ6IE9iamVjdFxyXG4gICAgKSB7XHJcbiAgICAgICAgdGhpcy5jaGVja1Nlc3Npb24oKTtcclxuICAgIH1cclxuXHJcbiAgICBwdWJsaWMgbmdPbkRlc3Ryb3koKTogdm9pZCB7XHJcbiAgICAgICAgdGhpcy5zdG9wSGVhcnRiZWF0KCk7XHJcbiAgICB9XHJcblxyXG4gICAgLyoqXHJcbiAgICAgKiBDaGVja3MgL2FwaS9hdXRoL21lIHRvIHNlZSBpZiB1c2VyIGlzIGxvZ2dlZCBpbi5cclxuICAgICAqIFNob3VsZCBiZSBjYWxsZWQgb24gYXBwIHN0YXJ0dXAuXHJcbiAgICAgKi9cclxuICAgIHB1YmxpYyBjaGVja1Nlc3Npb24oKTogdm9pZCB7XHJcbiAgICAgICAgaWYgKCFpc1BsYXRmb3JtQnJvd3Nlcih0aGlzLnBsYXRmb3JtSWQpKSB7XHJcbiAgICAgICAgICAgIHRoaXMuX2lzTG9hZGluZy5zZXQoZmFsc2UpO1xyXG4gICAgICAgICAgICB0aGlzLmluaXRpYWxpemVkJC5uZXh0KHRydWUpO1xyXG4gICAgICAgICAgICByZXR1cm47XHJcbiAgICAgICAgfVxyXG5cclxuICAgICAgICB0aGlzLl9pc0xvYWRpbmcuc2V0KHRydWUpO1xyXG4gICAgICAgIHRoaXMuZmV0Y2hVc2VyKCkuc3Vic2NyaWJlKCgpID0+IHtcclxuICAgICAgICAgICAgdGhpcy5faXNMb2FkaW5nLnNldChmYWxzZSk7XHJcbiAgICAgICAgICAgIHRoaXMuaW5pdGlhbGl6ZWQkLm5leHQodHJ1ZSk7XHJcbiAgICAgICAgfSk7XHJcbiAgICB9XHJcblxyXG4gICAgcHJpdmF0ZSBmZXRjaFVzZXIoKTogT2JzZXJ2YWJsZTxQcmltdXNVc2VyIHwgbnVsbD4ge1xyXG4gICAgICAgIGNvbnN0IGVuZHBvaW50ID0gYCR7dGhpcy5jb25maWcuYXBpVXJsfS9hcGkvYXV0aC9tZWA7XHJcbiAgICAgICAgcmV0dXJuIHRoaXMuaHR0cC5nZXQ8UHJpbXVzVXNlcj4oZW5kcG9pbnQsIHsgd2l0aENyZWRlbnRpYWxzOiB0cnVlIH0pLnBpcGUoXHJcbiAgICAgICAgICAgIHRhcCh1c2VyID0+IHRoaXMudXBkYXRlU3RhdGUodXNlcikpLFxyXG4gICAgICAgICAgICBjYXRjaEVycm9yKCgpID0+IHtcclxuICAgICAgICAgICAgICAgIHRoaXMudXBkYXRlU3RhdGUobnVsbCk7XHJcbiAgICAgICAgICAgICAgICByZXR1cm4gb2YobnVsbCk7XHJcbiAgICAgICAgICAgIH0pXHJcbiAgICAgICAgKTtcclxuICAgIH1cclxuXHJcbiAgICAvKipcclxuICAgICAqIFBlcmZvcm1zIGEgbG9jYWwgbG9naW4gKHVzZXJuYW1lL3Bhc3N3b3JkKSB2aWEgdGhlIEJhY2tlbmQgQnJva2VyLlxyXG4gICAgICogVGhlIGJhY2tlbmQgbXVzdCBhY2NlcHQgYSBQT1NUIHRvIC9hcGkvYXV0aC9sb2dpbiBhbmQgc2V0IGEgY29va2llLlxyXG4gICAgICovXHJcbiAgICBwdWJsaWMgbG9naW4oY3JlZGVudGlhbHM6IFJlY29yZDxzdHJpbmcsIGFueT4pOiBPYnNlcnZhYmxlPFByaW11c1VzZXIgfCBudWxsPiB7XHJcbiAgICAgICAgY29uc3QgZW5kcG9pbnQgPSBgJHt0aGlzLmNvbmZpZy5hcGlVcmx9L2FwaS9hdXRoL2xvZ2luYDtcclxuICAgICAgICAvLyBXZSBleHBlY3QgdGhlIGJhY2tlbmQgdG8gcmV0dXJuIHRoZSBVc2VyIG9iamVjdCBvbiBzdWNjZXNzIChvciB3ZSBmZXRjaCBpdClcclxuICAgICAgICAvLyBJZiB0aGUgYmFja2VuZCByZXR1cm5zIGp1c3QgMjAwIE9LLCB3ZSBtaWdodCBuZWVkIHRvIGNoYWluIGEgZmV0Y2hVc2VyKClcclxuICAgICAgICByZXR1cm4gdGhpcy5odHRwLnBvc3Q8UHJpbXVzVXNlcj4oZW5kcG9pbnQsIGNyZWRlbnRpYWxzLCB7IHdpdGhDcmVkZW50aWFsczogdHJ1ZSB9KS5waXBlKFxyXG4gICAgICAgICAgICB0YXAodXNlciA9PiB7XHJcbiAgICAgICAgICAgICAgICAvLyBJZiB0aGUgYmFja2VuZCByZXR1cm5zIHRoZSB1c2VyIGRpcmVjdGx5LCBncmVhdC5cclxuICAgICAgICAgICAgICAgIC8vIElmIGl0IHJldHVybnMgYSBnZW5lcmljIHN1Y2Nlc3MsIHdlIG1pZ2h0IGdldCAnbnVsbCcgaGVyZSBpZiB0eXBpbmcgZG9lc24ndCBtYXRjaC5cclxuICAgICAgICAgICAgICAgIC8vIEZvciBzYWZldHksIGlmIHdlIGdldCBhIHN1Y2Nlc3MgcmVzcG9uc2UsIHdlIGNhbiBhbHNvIGZvcmNlLWZldGNoIHRoZSB1c2VyIHRvIGJlIHN1cmUuXHJcbiAgICAgICAgICAgICAgICBpZiAodXNlciAmJiB1c2VyLmVtYWlsKSB7XHJcbiAgICAgICAgICAgICAgICAgICAgdGhpcy51cGRhdGVTdGF0ZSh1c2VyKTtcclxuICAgICAgICAgICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICAgICAgICAgICAgLy8gRmFsbGJhY2s6IEZldGNoIHVzZXIgaW5mbyBpZiByZXNwb25zZSB3YXMganVzdCB7IHN1Y2Nlc3M6IHRydWUgfVxyXG4gICAgICAgICAgICAgICAgICAgIHRoaXMuZmV0Y2hVc2VyKCkuc3Vic2NyaWJlKCk7XHJcbiAgICAgICAgICAgICAgICB9XHJcbiAgICAgICAgICAgIH0pXHJcbiAgICAgICAgKTtcclxuICAgIH1cclxuXHJcbiAgICAvKipcclxuICAgICAqIFJlZGlyZWN0cyB0aGUgYnJvd3NlciB0byB0aGUgYmFja2VuZCBsb2dpbiBlbmRwb2ludCBmb3IgdGhlIHByb3ZpZGVyLlxyXG4gICAgICogZS5nLiAvYXBpL2F1dGgvYXp1cmUsIC9hcGkvYXV0aC9hdXRoMFxyXG4gICAgICovXHJcbiAgICBwdWJsaWMgbG9naW5XaXRoUHJvdmlkZXIocHJvdmlkZXI6IHN0cmluZywgcmVkaXJlY3RVcmw/OiBzdHJpbmcpOiB2b2lkIHtcclxuICAgICAgICBjb25zdCByZXR1cm5VcmwgPSByZWRpcmVjdFVybCB8fCB0aGlzLnJvdXRlci51cmw7XHJcbiAgICAgICAgLy8gV2UgY2FuIHBhc3MgcmV0dXJuVXJsIGlmIGJhY2tlbmQgc3VwcG9ydHMgaXQsIHVzdWFsbHkgY29uZmlndXJlZCBnbG9iYWxseVxyXG4gICAgICAgIC8vIG9yIHZpYSBhIHF1ZXJ5IHBhcmFtIGhhbmRsZWQgYnkgdGhlIFJlc29sdXRpb24gbG9naWMuXHJcbiAgICAgICAgLy8gRm9yIG5vdyB3ZSBhc3N1bWUgc3RhbmRhcmQgZmxvdyBvciBiYXNpYyByZWRpcmVjdC5cclxuXHJcbiAgICAgICAgY29uc3QgdGFyZ2V0ID0gYCR7dGhpcy5jb25maWcuYXBpVXJsfS9hcGkvYXV0aC8ke3Byb3ZpZGVyfWA7XHJcbiAgICAgICAgd2luZG93LmxvY2F0aW9uLmhyZWYgPSB0YXJnZXQ7XHJcbiAgICB9XHJcblxyXG4gICAgcHVibGljIGxvZ291dCgpOiB2b2lkIHtcclxuICAgICAgICBjb25zdCBlbmRwb2ludCA9IGAke3RoaXMuY29uZmlnLmFwaVVybH0vYXBpL2F1dGgvbG9nb3V0YDtcclxuICAgICAgICB0aGlzLmh0dHAucG9zdChlbmRwb2ludCwge30sIHtcclxuICAgICAgICAgICAgd2l0aENyZWRlbnRpYWxzOiB0cnVlXHJcbiAgICAgICAgICAgIC8vIEludGVyY2VwdG9yIHdpbGwgYWRkIENTUkYgaGVhZGVyIGF1dG9tYXRpY2FsbHlcclxuICAgICAgICB9KS5zdWJzY3JpYmUoe1xyXG4gICAgICAgICAgICBuZXh0OiAoKSA9PiB7XHJcbiAgICAgICAgICAgICAgICB0aGlzLnVwZGF0ZVN0YXRlKG51bGwpO1xyXG4gICAgICAgICAgICAgICAgY29uc3QgcmVkaXJlY3QgPSB0aGlzLmNvbmZpZy5sb2dvdXRSZWRpcmVjdCB8fCAnL2xvZ2luJztcclxuICAgICAgICAgICAgICAgIHRoaXMucm91dGVyLm5hdmlnYXRlQnlVcmwocmVkaXJlY3QpO1xyXG4gICAgICAgICAgICB9LFxyXG4gICAgICAgICAgICBlcnJvcjogKCkgPT4ge1xyXG4gICAgICAgICAgICAgICAgLy8gRm9yY2UgbG9nb3V0IHN0YXRlIGV2ZW4gaWYgQVBJIGZhaWxlZFxyXG4gICAgICAgICAgICAgICAgdGhpcy51cGRhdGVTdGF0ZShudWxsKTtcclxuICAgICAgICAgICAgICAgIGNvbnN0IHJlZGlyZWN0ID0gdGhpcy5jb25maWcubG9nb3V0UmVkaXJlY3QgfHwgJy9sb2dpbic7XHJcbiAgICAgICAgICAgICAgICB0aGlzLnJvdXRlci5uYXZpZ2F0ZUJ5VXJsKHJlZGlyZWN0KTtcclxuICAgICAgICAgICAgfVxyXG4gICAgICAgIH0pO1xyXG4gICAgfVxyXG5cclxuICAgIHByaXZhdGUgdXBkYXRlU3RhdGUodXNlcjogUHJpbXVzVXNlciB8IG51bGwpIHtcclxuICAgICAgICB0aGlzLl91c2VyLnNldCh1c2VyKTtcclxuICAgICAgICB0aGlzLnVzZXIkLm5leHQodXNlcik7XHJcblxyXG4gICAgICAgIC8vIEF1dG8tc3luYyBzaWduYWwgdG8gbG9hZGluZyBzdGF0ZVxyXG4gICAgICAgIGlmICh1c2VyKSB7XHJcbiAgICAgICAgICAgIHRoaXMuX2lzTG9hZGluZy5zZXQoZmFsc2UpO1xyXG4gICAgICAgICAgICB0aGlzLnN0YXJ0SGVhcnRiZWF0KCk7XHJcbiAgICAgICAgfSBlbHNlIHtcclxuICAgICAgICAgICAgdGhpcy5zdG9wSGVhcnRiZWF0KCk7XHJcbiAgICAgICAgfVxyXG4gICAgfVxyXG5cclxuICAgIC8vIENhbGxlZCBieSBJbnRlcmNlcHRvciBvbiA0MDFcclxuICAgIHB1YmxpYyBoYW5kbGVVbmF1dGhvcml6ZWQoKTogdm9pZCB7XHJcbiAgICAgICAgdGhpcy51cGRhdGVTdGF0ZShudWxsKTtcclxuICAgIH1cclxuXHJcbiAgICBwcml2YXRlIHN0YXJ0SGVhcnRiZWF0KCk6IHZvaWQge1xyXG4gICAgICAgIGlmICghaXNQbGF0Zm9ybUJyb3dzZXIodGhpcy5wbGF0Zm9ybUlkKSkgcmV0dXJuO1xyXG4gICAgICAgIGlmICh0aGlzLmhlYXJ0YmVhdFN1YikgcmV0dXJuOyAvLyBBbHJlYWR5IHJ1bm5pbmdcclxuXHJcbiAgICAgICAgdGhpcy5oZWFydGJlYXRTdWIgPSBpbnRlcnZhbCh0aGlzLkhFQVJUQkVBVF9JTlRFUlZBTCkuc3Vic2NyaWJlKCgpID0+IHtcclxuICAgICAgICAgICAgLy8gUXVpZXRseSBjaGVjayBzZXNzaW9uXHJcbiAgICAgICAgICAgIHRoaXMuZmV0Y2hVc2VyKCkuc3Vic2NyaWJlKCk7XHJcbiAgICAgICAgfSk7XHJcbiAgICB9XHJcblxyXG4gICAgcHJpdmF0ZSBzdG9wSGVhcnRiZWF0KCk6IHZvaWQge1xyXG4gICAgICAgIGlmICh0aGlzLmhlYXJ0YmVhdFN1Yikge1xyXG4gICAgICAgICAgICB0aGlzLmhlYXJ0YmVhdFN1Yi51bnN1YnNjcmliZSgpO1xyXG4gICAgICAgICAgICB0aGlzLmhlYXJ0YmVhdFN1YiA9IG51bGw7XHJcbiAgICAgICAgfVxyXG4gICAgfVxyXG59XHJcbiJdfQ==
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { InjectionToken } from '@angular/core';
|
|
2
|
+
export const PRIMUS_AUTH_CONFIG = new InjectionToken('PRIMUS_AUTH_CONFIG');
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJpbXVzLWF1dGgudG9rZW5zLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vYW5ndWxhci9wcmltdXMtYXV0aC9zcmMvbGliL3ByaW11cy1hdXRoLnRva2Vucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRy9DLE1BQU0sQ0FBQyxNQUFNLGtCQUFrQixHQUFHLElBQUksY0FBYyxDQUFtQixvQkFBb0IsQ0FBQyxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgSW5qZWN0aW9uVG9rZW4gfSBmcm9tICdAYW5ndWxhci9jb3JlJztcclxuaW1wb3J0IHsgUHJpbXVzQXV0aENvbmZpZyB9IGZyb20gJy4vcHJpbXVzLWF1dGgudHlwZXMnO1xyXG5cclxuZXhwb3J0IGNvbnN0IFBSSU1VU19BVVRIX0NPTkZJRyA9IG5ldyBJbmplY3Rpb25Ub2tlbjxQcmltdXNBdXRoQ29uZmlnPignUFJJTVVTX0FVVEhfQ09ORklHJyk7XHJcbiJdfQ==
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export {};
|
|
2
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJpbXVzLWF1dGgudHlwZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9hbmd1bGFyL3ByaW11cy1hdXRoL3NyYy9saWIvcHJpbXVzLWF1dGgudHlwZXMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBpbnRlcmZhY2UgUHJpbXVzQXV0aENvbmZpZyB7XHJcbiAgICBhcGlVcmw6IHN0cmluZztcclxuICAgIGxvZ2luUGF0aD86IHN0cmluZzsgLy8gRGVmYXVsdDogL2xvZ2luXHJcbiAgICBsb2dvdXRSZWRpcmVjdD86IHN0cmluZzsgLy8gRGVmYXVsdDogL2xvZ2luXHJcbn1cclxuXHJcbmV4cG9ydCBpbnRlcmZhY2UgUHJpbXVzVXNlciB7XHJcbiAgICBpZDogc3RyaW5nO1xyXG4gICAgZW1haWw6IHN0cmluZztcclxuICAgIG5hbWU/OiBzdHJpbmc7XHJcbiAgICByb2xlPzogc3RyaW5nO1xyXG4gICAgcHJvdmlkZXI/OiBzdHJpbmc7XHJcbiAgICBwaWN0dXJlPzogc3RyaW5nO1xyXG4gICAgW2tleTogc3RyaW5nXTogYW55O1xyXG59XHJcblxyXG5leHBvcnQgaW50ZXJmYWNlIEF1dGhTdGF0ZSB7XHJcbiAgICBpc0F1dGhlbnRpY2F0ZWQ6IGJvb2xlYW47XHJcbiAgICB1c2VyOiBQcmltdXNVc2VyIHwgbnVsbDtcclxuICAgIGlzTG9hZGluZzogYm9vbGVhbjtcclxufVxyXG4iXX0=
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated bundle index. Do not edit.
|
|
3
|
+
*/
|
|
4
|
+
export * from './public-api';
|
|
5
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJpbXVzLWFuZ3VsYXItYXV0aC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL2FuZ3VsYXIvcHJpbXVzLWF1dGgvc3JjL3ByaW11cy1hbmd1bGFyLWF1dGgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLGNBQWMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogR2VuZXJhdGVkIGJ1bmRsZSBpbmRleC4gRG8gbm90IGVkaXQuXG4gKi9cblxuZXhwb3J0ICogZnJvbSAnLi9wdWJsaWMtYXBpJztcbiJdfQ==
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Public API Surface of primus-angular-auth
|
|
3
|
+
*/
|
|
4
|
+
export * from './lib/primus-auth.service';
|
|
5
|
+
export * from './lib/primus-auth.interceptor';
|
|
6
|
+
export * from './lib/primus-auth.guard';
|
|
7
|
+
export * from './lib/primus-auth.provider';
|
|
8
|
+
export * from './lib/primus-auth.types';
|
|
9
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL2FuZ3VsYXIvcHJpbXVzLWF1dGgvc3JjL3B1YmxpYy1hcGkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLDJCQUEyQixDQUFDO0FBQzFDLGNBQWMsK0JBQStCLENBQUM7QUFDOUMsY0FBYyx5QkFBeUIsQ0FBQztBQUN4QyxjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMseUJBQXlCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKlxyXG4gKiBQdWJsaWMgQVBJIFN1cmZhY2Ugb2YgcHJpbXVzLWFuZ3VsYXItYXV0aFxyXG4gKi9cclxuXHJcbmV4cG9ydCAqIGZyb20gJy4vbGliL3ByaW11cy1hdXRoLnNlcnZpY2UnO1xyXG5leHBvcnQgKiBmcm9tICcuL2xpYi9wcmltdXMtYXV0aC5pbnRlcmNlcHRvcic7XHJcbmV4cG9ydCAqIGZyb20gJy4vbGliL3ByaW11cy1hdXRoLmd1YXJkJztcclxuZXhwb3J0ICogZnJvbSAnLi9saWIvcHJpbXVzLWF1dGgucHJvdmlkZXInO1xyXG5leHBvcnQgKiBmcm9tICcuL2xpYi9wcmltdXMtYXV0aC50eXBlcyc7XHJcbiJdfQ==
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, signal, computed, PLATFORM_ID, Inject, Injectable, inject, makeEnvironmentProviders, APP_INITIALIZER } from '@angular/core';
|
|
3
|
+
import * as i3 from '@angular/common';
|
|
4
|
+
import { isPlatformBrowser } from '@angular/common';
|
|
5
|
+
import { map, tap, catchError, filter, take, switchMap } from 'rxjs/operators';
|
|
6
|
+
import { BehaviorSubject, of, interval, catchError as catchError$1, throwError } from 'rxjs';
|
|
7
|
+
import * as i1 from '@angular/common/http';
|
|
8
|
+
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
|
9
|
+
import * as i2 from '@angular/router';
|
|
10
|
+
import { Router } from '@angular/router';
|
|
11
|
+
|
|
12
|
+
const PRIMUS_AUTH_CONFIG = new InjectionToken('PRIMUS_AUTH_CONFIG');
|
|
13
|
+
|
|
14
|
+
class PrimusAuthService {
|
|
15
|
+
http;
|
|
16
|
+
router;
|
|
17
|
+
location;
|
|
18
|
+
config;
|
|
19
|
+
platformId;
|
|
20
|
+
// Reactive State (Signals)
|
|
21
|
+
_user = signal(null);
|
|
22
|
+
_isLoading = signal(true);
|
|
23
|
+
user = this._user.asReadonly();
|
|
24
|
+
isLoading = this._isLoading.asReadonly();
|
|
25
|
+
isAuthenticated = computed(() => !!this._user());
|
|
26
|
+
// Legacy Observable support
|
|
27
|
+
user$ = new BehaviorSubject(null);
|
|
28
|
+
isAuthenticated$ = this.user$.pipe(map(u => !!u));
|
|
29
|
+
initialized$ = new BehaviorSubject(false);
|
|
30
|
+
heartbeatSub = null;
|
|
31
|
+
HEARTBEAT_INTERVAL = 5 * 60 * 1000; // 5 minutes
|
|
32
|
+
constructor(http, router, location, config, platformId) {
|
|
33
|
+
this.http = http;
|
|
34
|
+
this.router = router;
|
|
35
|
+
this.location = location;
|
|
36
|
+
this.config = config;
|
|
37
|
+
this.platformId = platformId;
|
|
38
|
+
this.checkSession();
|
|
39
|
+
}
|
|
40
|
+
ngOnDestroy() {
|
|
41
|
+
this.stopHeartbeat();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Checks /api/auth/me to see if user is logged in.
|
|
45
|
+
* Should be called on app startup.
|
|
46
|
+
*/
|
|
47
|
+
checkSession() {
|
|
48
|
+
if (!isPlatformBrowser(this.platformId)) {
|
|
49
|
+
this._isLoading.set(false);
|
|
50
|
+
this.initialized$.next(true);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this._isLoading.set(true);
|
|
54
|
+
this.fetchUser().subscribe(() => {
|
|
55
|
+
this._isLoading.set(false);
|
|
56
|
+
this.initialized$.next(true);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
fetchUser() {
|
|
60
|
+
const endpoint = `${this.config.apiUrl}/api/auth/me`;
|
|
61
|
+
return this.http.get(endpoint, { withCredentials: true }).pipe(tap(user => this.updateState(user)), catchError(() => {
|
|
62
|
+
this.updateState(null);
|
|
63
|
+
return of(null);
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Performs a local login (username/password) via the Backend Broker.
|
|
68
|
+
* The backend must accept a POST to /api/auth/login and set a cookie.
|
|
69
|
+
*/
|
|
70
|
+
login(credentials) {
|
|
71
|
+
const endpoint = `${this.config.apiUrl}/api/auth/login`;
|
|
72
|
+
// We expect the backend to return the User object on success (or we fetch it)
|
|
73
|
+
// If the backend returns just 200 OK, we might need to chain a fetchUser()
|
|
74
|
+
return this.http.post(endpoint, credentials, { withCredentials: true }).pipe(tap(user => {
|
|
75
|
+
// If the backend returns the user directly, great.
|
|
76
|
+
// If it returns a generic success, we might get 'null' here if typing doesn't match.
|
|
77
|
+
// For safety, if we get a success response, we can also force-fetch the user to be sure.
|
|
78
|
+
if (user && user.email) {
|
|
79
|
+
this.updateState(user);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
// Fallback: Fetch user info if response was just { success: true }
|
|
83
|
+
this.fetchUser().subscribe();
|
|
84
|
+
}
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Redirects the browser to the backend login endpoint for the provider.
|
|
89
|
+
* e.g. /api/auth/azure, /api/auth/auth0
|
|
90
|
+
*/
|
|
91
|
+
loginWithProvider(provider, redirectUrl) {
|
|
92
|
+
const returnUrl = redirectUrl || this.router.url;
|
|
93
|
+
// We can pass returnUrl if backend supports it, usually configured globally
|
|
94
|
+
// or via a query param handled by the Resolution logic.
|
|
95
|
+
// For now we assume standard flow or basic redirect.
|
|
96
|
+
const target = `${this.config.apiUrl}/api/auth/${provider}`;
|
|
97
|
+
window.location.href = target;
|
|
98
|
+
}
|
|
99
|
+
logout() {
|
|
100
|
+
const endpoint = `${this.config.apiUrl}/api/auth/logout`;
|
|
101
|
+
this.http.post(endpoint, {}, {
|
|
102
|
+
withCredentials: true
|
|
103
|
+
// Interceptor will add CSRF header automatically
|
|
104
|
+
}).subscribe({
|
|
105
|
+
next: () => {
|
|
106
|
+
this.updateState(null);
|
|
107
|
+
const redirect = this.config.logoutRedirect || '/login';
|
|
108
|
+
this.router.navigateByUrl(redirect);
|
|
109
|
+
},
|
|
110
|
+
error: () => {
|
|
111
|
+
// Force logout state even if API failed
|
|
112
|
+
this.updateState(null);
|
|
113
|
+
const redirect = this.config.logoutRedirect || '/login';
|
|
114
|
+
this.router.navigateByUrl(redirect);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
updateState(user) {
|
|
119
|
+
this._user.set(user);
|
|
120
|
+
this.user$.next(user);
|
|
121
|
+
// Auto-sync signal to loading state
|
|
122
|
+
if (user) {
|
|
123
|
+
this._isLoading.set(false);
|
|
124
|
+
this.startHeartbeat();
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
this.stopHeartbeat();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Called by Interceptor on 401
|
|
131
|
+
handleUnauthorized() {
|
|
132
|
+
this.updateState(null);
|
|
133
|
+
}
|
|
134
|
+
startHeartbeat() {
|
|
135
|
+
if (!isPlatformBrowser(this.platformId))
|
|
136
|
+
return;
|
|
137
|
+
if (this.heartbeatSub)
|
|
138
|
+
return; // Already running
|
|
139
|
+
this.heartbeatSub = interval(this.HEARTBEAT_INTERVAL).subscribe(() => {
|
|
140
|
+
// Quietly check session
|
|
141
|
+
this.fetchUser().subscribe();
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
stopHeartbeat() {
|
|
145
|
+
if (this.heartbeatSub) {
|
|
146
|
+
this.heartbeatSub.unsubscribe();
|
|
147
|
+
this.heartbeatSub = null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PrimusAuthService, deps: [{ token: i1.HttpClient }, { token: i2.Router }, { token: i3.Location }, { token: PRIMUS_AUTH_CONFIG }, { token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
151
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PrimusAuthService, providedIn: 'root' });
|
|
152
|
+
}
|
|
153
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PrimusAuthService, decorators: [{
|
|
154
|
+
type: Injectable,
|
|
155
|
+
args: [{
|
|
156
|
+
providedIn: 'root'
|
|
157
|
+
}]
|
|
158
|
+
}], ctorParameters: () => [{ type: i1.HttpClient }, { type: i2.Router }, { type: i3.Location }, { type: undefined, decorators: [{
|
|
159
|
+
type: Inject,
|
|
160
|
+
args: [PRIMUS_AUTH_CONFIG]
|
|
161
|
+
}] }, { type: Object, decorators: [{
|
|
162
|
+
type: Inject,
|
|
163
|
+
args: [PLATFORM_ID]
|
|
164
|
+
}] }] });
|
|
165
|
+
|
|
166
|
+
const primusAuthInterceptor = (req, next) => {
|
|
167
|
+
const authService = inject(PrimusAuthService);
|
|
168
|
+
// 1. Add CSRF Header to non-GET requests
|
|
169
|
+
// Double-Submit Cookie Pattern: Read XSRF-TOKEN cookie and mirror in header.
|
|
170
|
+
let request = req;
|
|
171
|
+
if (req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'OPTIONS') {
|
|
172
|
+
const token = getCookie('XSRF-TOKEN') || '';
|
|
173
|
+
request = req.clone({
|
|
174
|
+
setHeaders: {
|
|
175
|
+
'X-Primus-CSRF': token
|
|
176
|
+
},
|
|
177
|
+
withCredentials: true // Ensure cookies are sent
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
// Determine if we need credentials for GETs?
|
|
182
|
+
// Usually yes for API calls to our backend.
|
|
183
|
+
// We assume calls to same origin or API URL need creds.
|
|
184
|
+
request = req.clone({
|
|
185
|
+
withCredentials: true
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
return next(request).pipe(catchError$1((error) => {
|
|
189
|
+
// 2. Handle 401 Unauthorized
|
|
190
|
+
if (error.status === 401) {
|
|
191
|
+
authService.handleUnauthorized();
|
|
192
|
+
}
|
|
193
|
+
return throwError(() => error);
|
|
194
|
+
}));
|
|
195
|
+
};
|
|
196
|
+
function getCookie(name) {
|
|
197
|
+
try {
|
|
198
|
+
if (typeof document === 'undefined')
|
|
199
|
+
return null;
|
|
200
|
+
const matches = document.cookie.match(new RegExp('(?:^|; )' + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + '=([^;]*)'));
|
|
201
|
+
return matches ? decodeURIComponent(matches[1]) : null;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const primusAuthGuard = (route, state) => {
|
|
209
|
+
const auth = inject(PrimusAuthService);
|
|
210
|
+
const router = inject(Router);
|
|
211
|
+
const config = inject(PRIMUS_AUTH_CONFIG);
|
|
212
|
+
// Wait for initialization to complete, then check authentication status
|
|
213
|
+
return auth.initialized$.pipe(filter((isInit) => isInit), // Wait until true
|
|
214
|
+
take(1), switchMap(() => auth.isAuthenticated$), take(1), map((isAuthenticated) => {
|
|
215
|
+
if (isAuthenticated) {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
// Redirect to login
|
|
219
|
+
const loginPath = config.loginPath || '/login';
|
|
220
|
+
return router.createUrlTree([loginPath], {
|
|
221
|
+
queryParams: { returnUrl: state.url }
|
|
222
|
+
});
|
|
223
|
+
}));
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
function providePrimusAuth(config) {
|
|
227
|
+
return makeEnvironmentProviders([
|
|
228
|
+
{
|
|
229
|
+
provide: PRIMUS_AUTH_CONFIG,
|
|
230
|
+
useValue: config
|
|
231
|
+
},
|
|
232
|
+
PrimusAuthService,
|
|
233
|
+
provideHttpClient(withInterceptors([primusAuthInterceptor])),
|
|
234
|
+
{
|
|
235
|
+
provide: APP_INITIALIZER,
|
|
236
|
+
useFactory: (auth, platform) => () => {
|
|
237
|
+
// We trigger checkSession in constructor, but we can also do it here if we want to await it.
|
|
238
|
+
// However, awaiting APP_INITIALIZER blocks the whole app render.
|
|
239
|
+
// Better to let it run in background and use the Guard to block specific routes.
|
|
240
|
+
// So we just ensure service is instantiated.
|
|
241
|
+
// Actually, checkSession is already called in constructor.
|
|
242
|
+
// This APP_INITIALIZER forces the service to be created immediately on app start.
|
|
243
|
+
},
|
|
244
|
+
deps: [PrimusAuthService, PLATFORM_ID],
|
|
245
|
+
multi: true
|
|
246
|
+
}
|
|
247
|
+
]);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/*
|
|
251
|
+
* Public API Surface of primus-angular-auth
|
|
252
|
+
*/
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Generated bundle index. Do not edit.
|
|
256
|
+
*/
|
|
257
|
+
|
|
258
|
+
export { PrimusAuthService, primusAuthGuard, primusAuthInterceptor, providePrimusAuth };
|
|
259
|
+
//# sourceMappingURL=primus-angular-auth.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"primus-angular-auth.mjs","sources":["../../../angular/primus-auth/src/lib/primus-auth.tokens.ts","../../../angular/primus-auth/src/lib/primus-auth.service.ts","../../../angular/primus-auth/src/lib/primus-auth.interceptor.ts","../../../angular/primus-auth/src/lib/primus-auth.guard.ts","../../../angular/primus-auth/src/lib/primus-auth.provider.ts","../../../angular/primus-auth/src/public-api.ts","../../../angular/primus-auth/src/primus-angular-auth.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\r\nimport { PrimusAuthConfig } from './primus-auth.types';\r\n\r\nexport const PRIMUS_AUTH_CONFIG = new InjectionToken<PrimusAuthConfig>('PRIMUS_AUTH_CONFIG');\r\n","import { Injectable, Inject, signal, computed, PLATFORM_ID, OnDestroy } from '@angular/core';\r\nimport { HttpClient } from '@angular/common/http';\r\nimport { Router } from '@angular/router';\r\nimport { isPlatformBrowser, Location } from '@angular/common';\r\nimport { catchError, map, tap } from 'rxjs/operators';\r\nimport { Observable, of, BehaviorSubject, Subscription, interval } from 'rxjs';\r\nimport { PrimusAuthConfig, PrimusUser } from './primus-auth.types';\r\nimport { PRIMUS_AUTH_CONFIG } from './primus-auth.tokens';\r\n\r\n@Injectable({\r\n providedIn: 'root'\r\n})\r\nexport class PrimusAuthService implements OnDestroy {\r\n // Reactive State (Signals)\r\n private _user = signal<PrimusUser | null>(null);\r\n private _isLoading = signal<boolean>(true);\r\n\r\n public readonly user = this._user.asReadonly();\r\n public readonly isLoading = this._isLoading.asReadonly();\r\n public readonly isAuthenticated = computed(() => !!this._user());\r\n\r\n // Legacy Observable support\r\n public readonly user$ = new BehaviorSubject<PrimusUser | null>(null);\r\n public readonly isAuthenticated$ = this.user$.pipe(map(u => !!u));\r\n public readonly initialized$ = new BehaviorSubject<boolean>(false);\r\n\r\n private heartbeatSub: Subscription | null = null;\r\n private readonly HEARTBEAT_INTERVAL = 5 * 60 * 1000; // 5 minutes\r\n\r\n constructor(\r\n private http: HttpClient,\r\n private router: Router,\r\n private location: Location,\r\n @Inject(PRIMUS_AUTH_CONFIG) private config: PrimusAuthConfig,\r\n @Inject(PLATFORM_ID) private platformId: Object\r\n ) {\r\n this.checkSession();\r\n }\r\n\r\n public ngOnDestroy(): void {\r\n this.stopHeartbeat();\r\n }\r\n\r\n /**\r\n * Checks /api/auth/me to see if user is logged in.\r\n * Should be called on app startup.\r\n */\r\n public checkSession(): void {\r\n if (!isPlatformBrowser(this.platformId)) {\r\n this._isLoading.set(false);\r\n this.initialized$.next(true);\r\n return;\r\n }\r\n\r\n this._isLoading.set(true);\r\n this.fetchUser().subscribe(() => {\r\n this._isLoading.set(false);\r\n this.initialized$.next(true);\r\n });\r\n }\r\n\r\n private fetchUser(): Observable<PrimusUser | null> {\r\n const endpoint = `${this.config.apiUrl}/api/auth/me`;\r\n return this.http.get<PrimusUser>(endpoint, { withCredentials: true }).pipe(\r\n tap(user => this.updateState(user)),\r\n catchError(() => {\r\n this.updateState(null);\r\n return of(null);\r\n })\r\n );\r\n }\r\n\r\n /**\r\n * Performs a local login (username/password) via the Backend Broker.\r\n * The backend must accept a POST to /api/auth/login and set a cookie.\r\n */\r\n public login(credentials: Record<string, any>): Observable<PrimusUser | null> {\r\n const endpoint = `${this.config.apiUrl}/api/auth/login`;\r\n // We expect the backend to return the User object on success (or we fetch it)\r\n // If the backend returns just 200 OK, we might need to chain a fetchUser()\r\n return this.http.post<PrimusUser>(endpoint, credentials, { withCredentials: true }).pipe(\r\n tap(user => {\r\n // If the backend returns the user directly, great.\r\n // If it returns a generic success, we might get 'null' here if typing doesn't match.\r\n // For safety, if we get a success response, we can also force-fetch the user to be sure.\r\n if (user && user.email) {\r\n this.updateState(user);\r\n } else {\r\n // Fallback: Fetch user info if response was just { success: true }\r\n this.fetchUser().subscribe();\r\n }\r\n })\r\n );\r\n }\r\n\r\n /**\r\n * Redirects the browser to the backend login endpoint for the provider.\r\n * e.g. /api/auth/azure, /api/auth/auth0\r\n */\r\n public loginWithProvider(provider: string, redirectUrl?: string): void {\r\n const returnUrl = redirectUrl || this.router.url;\r\n // We can pass returnUrl if backend supports it, usually configured globally\r\n // or via a query param handled by the Resolution logic.\r\n // For now we assume standard flow or basic redirect.\r\n\r\n const target = `${this.config.apiUrl}/api/auth/${provider}`;\r\n window.location.href = target;\r\n }\r\n\r\n public logout(): void {\r\n const endpoint = `${this.config.apiUrl}/api/auth/logout`;\r\n this.http.post(endpoint, {}, {\r\n withCredentials: true\r\n // Interceptor will add CSRF header automatically\r\n }).subscribe({\r\n next: () => {\r\n this.updateState(null);\r\n const redirect = this.config.logoutRedirect || '/login';\r\n this.router.navigateByUrl(redirect);\r\n },\r\n error: () => {\r\n // Force logout state even if API failed\r\n this.updateState(null);\r\n const redirect = this.config.logoutRedirect || '/login';\r\n this.router.navigateByUrl(redirect);\r\n }\r\n });\r\n }\r\n\r\n private updateState(user: PrimusUser | null) {\r\n this._user.set(user);\r\n this.user$.next(user);\r\n\r\n // Auto-sync signal to loading state\r\n if (user) {\r\n this._isLoading.set(false);\r\n this.startHeartbeat();\r\n } else {\r\n this.stopHeartbeat();\r\n }\r\n }\r\n\r\n // Called by Interceptor on 401\r\n public handleUnauthorized(): void {\r\n this.updateState(null);\r\n }\r\n\r\n private startHeartbeat(): void {\r\n if (!isPlatformBrowser(this.platformId)) return;\r\n if (this.heartbeatSub) return; // Already running\r\n\r\n this.heartbeatSub = interval(this.HEARTBEAT_INTERVAL).subscribe(() => {\r\n // Quietly check session\r\n this.fetchUser().subscribe();\r\n });\r\n }\r\n\r\n private stopHeartbeat(): void {\r\n if (this.heartbeatSub) {\r\n this.heartbeatSub.unsubscribe();\r\n this.heartbeatSub = null;\r\n }\r\n }\r\n}\r\n","import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';\r\nimport { inject } from '@angular/core';\r\nimport { catchError, throwError } from 'rxjs';\r\nimport { PrimusAuthService } from './primus-auth.service';\r\n\r\nexport const primusAuthInterceptor: HttpInterceptorFn = (req, next) => {\r\n const authService = inject(PrimusAuthService);\r\n\r\n // 1. Add CSRF Header to non-GET requests\r\n // Double-Submit Cookie Pattern: Read XSRF-TOKEN cookie and mirror in header.\r\n let request = req;\r\n if (req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'OPTIONS') {\r\n const token = getCookie('XSRF-TOKEN') || '';\r\n request = req.clone({\r\n setHeaders: {\r\n 'X-Primus-CSRF': token\r\n },\r\n withCredentials: true // Ensure cookies are sent\r\n });\r\n } else {\r\n // Determine if we need credentials for GETs?\r\n // Usually yes for API calls to our backend.\r\n // We assume calls to same origin or API URL need creds.\r\n request = req.clone({\r\n withCredentials: true\r\n });\r\n }\r\n \r\n return next(request).pipe(\r\n catchError((error: HttpErrorResponse) => {\r\n // 2. Handle 401 Unauthorized\r\n if (error.status === 401) {\r\n authService.handleUnauthorized();\r\n }\r\n return throwError(() => error);\r\n })\r\n );\r\n };\r\n \r\n function getCookie(name: string): string | null {\r\n try {\r\n if (typeof document === 'undefined') return null;\r\n const matches = document.cookie.match(new RegExp('(?:^|; )' + name.replace(/([\\.$?*|{}\\(\\)\\[\\]\\\\\\/\\+^])/g, '\\\\$1') + '=([^;]*)'));\r\n return matches ? decodeURIComponent(matches[1]) : null;\r\n } catch {\r\n return null;\r\n }\r\n }\r\n","import { CanActivateFn, Router, UrlTree, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';\r\nimport { inject } from '@angular/core';\r\nimport { PrimusAuthService } from './primus-auth.service';\r\nimport { map, filter, switchMap, take } from 'rxjs/operators';\r\nimport { PRIMUS_AUTH_CONFIG } from './primus-auth.tokens';\r\n\r\nexport const primusAuthGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {\r\n const auth = inject(PrimusAuthService);\r\n const router = inject(Router);\r\n const config = inject(PRIMUS_AUTH_CONFIG);\r\n\r\n // Wait for initialization to complete, then check authentication status\r\n return auth.initialized$.pipe(\r\n filter((isInit: boolean) => isInit), // Wait until true\r\n take(1),\r\n switchMap(() => auth.isAuthenticated$),\r\n take(1),\r\n map((isAuthenticated: boolean) => {\r\n if (isAuthenticated) {\r\n return true;\r\n }\r\n\r\n // Redirect to login\r\n const loginPath = config.loginPath || '/login';\r\n return router.createUrlTree([loginPath], {\r\n queryParams: { returnUrl: state.url }\r\n });\r\n })\r\n );\r\n};\r\n","import { EnvironmentProviders, makeEnvironmentProviders, Provider, APP_INITIALIZER, PLATFORM_ID, inject } from '@angular/core';\r\nimport { provideHttpClient, withInterceptors } from '@angular/common/http';\r\nimport { PrimusAuthConfig } from './primus-auth.types';\r\nimport { PRIMUS_AUTH_CONFIG } from './primus-auth.tokens';\r\nimport { PrimusAuthService } from './primus-auth.service';\r\nimport { primusAuthInterceptor } from './primus-auth.interceptor';\r\nimport { isPlatformBrowser } from '@angular/common';\r\n\r\nexport function providePrimusAuth(config: PrimusAuthConfig): EnvironmentProviders {\r\n return makeEnvironmentProviders([\r\n {\r\n provide: PRIMUS_AUTH_CONFIG,\r\n useValue: config\r\n },\r\n PrimusAuthService,\r\n provideHttpClient(\r\n withInterceptors([primusAuthInterceptor])\r\n ),\r\n {\r\n provide: APP_INITIALIZER,\r\n useFactory: (auth: PrimusAuthService, platform: Object) => () => {\r\n // We trigger checkSession in constructor, but we can also do it here if we want to await it.\r\n // However, awaiting APP_INITIALIZER blocks the whole app render.\r\n // Better to let it run in background and use the Guard to block specific routes.\r\n // So we just ensure service is instantiated.\r\n\r\n // Actually, checkSession is already called in constructor.\r\n // This APP_INITIALIZER forces the service to be created immediately on app start.\r\n },\r\n deps: [PrimusAuthService, PLATFORM_ID],\r\n multi: true\r\n }\r\n ]);\r\n}\r\n","/*\r\n * Public API Surface of primus-angular-auth\r\n */\r\n\r\nexport * from './lib/primus-auth.service';\r\nexport * from './lib/primus-auth.interceptor';\r\nexport * from './lib/primus-auth.guard';\r\nexport * from './lib/primus-auth.provider';\r\nexport * from './lib/primus-auth.types';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["catchError"],"mappings":"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"}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { OnDestroy } from '@angular/core';
|
|
2
|
+
import { HttpClient } from '@angular/common/http';
|
|
3
|
+
import { Router } from '@angular/router';
|
|
4
|
+
import { Location } from '@angular/common';
|
|
5
|
+
import { Observable, BehaviorSubject } from 'rxjs';
|
|
6
|
+
import { PrimusAuthConfig, PrimusUser } from './primus-auth.types';
|
|
7
|
+
import * as i0 from "@angular/core";
|
|
8
|
+
export declare class PrimusAuthService implements OnDestroy {
|
|
9
|
+
private http;
|
|
10
|
+
private router;
|
|
11
|
+
private location;
|
|
12
|
+
private config;
|
|
13
|
+
private platformId;
|
|
14
|
+
private _user;
|
|
15
|
+
private _isLoading;
|
|
16
|
+
readonly user: import("@angular/core").Signal<PrimusUser>;
|
|
17
|
+
readonly isLoading: import("@angular/core").Signal<boolean>;
|
|
18
|
+
readonly isAuthenticated: import("@angular/core").Signal<boolean>;
|
|
19
|
+
readonly user$: BehaviorSubject<PrimusUser>;
|
|
20
|
+
readonly isAuthenticated$: Observable<boolean>;
|
|
21
|
+
readonly initialized$: BehaviorSubject<boolean>;
|
|
22
|
+
private heartbeatSub;
|
|
23
|
+
private readonly HEARTBEAT_INTERVAL;
|
|
24
|
+
constructor(http: HttpClient, router: Router, location: Location, config: PrimusAuthConfig, platformId: Object);
|
|
25
|
+
ngOnDestroy(): void;
|
|
26
|
+
/**
|
|
27
|
+
* Checks /api/auth/me to see if user is logged in.
|
|
28
|
+
* Should be called on app startup.
|
|
29
|
+
*/
|
|
30
|
+
checkSession(): void;
|
|
31
|
+
private fetchUser;
|
|
32
|
+
/**
|
|
33
|
+
* Performs a local login (username/password) via the Backend Broker.
|
|
34
|
+
* The backend must accept a POST to /api/auth/login and set a cookie.
|
|
35
|
+
*/
|
|
36
|
+
login(credentials: Record<string, any>): Observable<PrimusUser | null>;
|
|
37
|
+
/**
|
|
38
|
+
* Redirects the browser to the backend login endpoint for the provider.
|
|
39
|
+
* e.g. /api/auth/azure, /api/auth/auth0
|
|
40
|
+
*/
|
|
41
|
+
loginWithProvider(provider: string, redirectUrl?: string): void;
|
|
42
|
+
logout(): void;
|
|
43
|
+
private updateState;
|
|
44
|
+
handleUnauthorized(): void;
|
|
45
|
+
private startHeartbeat;
|
|
46
|
+
private stopHeartbeat;
|
|
47
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<PrimusAuthService, never>;
|
|
48
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<PrimusAuthService>;
|
|
49
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface PrimusAuthConfig {
|
|
2
|
+
apiUrl: string;
|
|
3
|
+
loginPath?: string;
|
|
4
|
+
logoutRedirect?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface PrimusUser {
|
|
7
|
+
id: string;
|
|
8
|
+
email: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
role?: string;
|
|
11
|
+
provider?: string;
|
|
12
|
+
picture?: string;
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
}
|
|
15
|
+
export interface AuthState {
|
|
16
|
+
isAuthenticated: boolean;
|
|
17
|
+
user: PrimusUser | null;
|
|
18
|
+
isLoading: boolean;
|
|
19
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "primus-angular-auth",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Enterprise-grade BFF authentication client for Primus SaaS Framework in Angular.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"peerDependencies": {
|
|
7
|
+
"@angular/common": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0",
|
|
8
|
+
"@angular/core": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0",
|
|
9
|
+
"@angular/router": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0",
|
|
10
|
+
"rxjs": "^7.8.0"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"tslib": "^2.3.0"
|
|
14
|
+
},
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"module": "fesm2022/primus-angular-auth.mjs",
|
|
17
|
+
"typings": "index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
"./package.json": {
|
|
20
|
+
"default": "./package.json"
|
|
21
|
+
},
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./index.d.ts",
|
|
24
|
+
"esm2022": "./esm2022/primus-angular-auth.mjs",
|
|
25
|
+
"esm": "./esm2022/primus-angular-auth.mjs",
|
|
26
|
+
"default": "./fesm2022/primus-angular-auth.mjs"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|