@nauth-toolkit/client-angular 0.1.57 → 0.1.59
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/auth.guard.mjs +58 -23
- package/esm2022/lib/social-redirect-callback.guard.mjs +28 -24
- package/esm2022/ngmodule/auth.service.mjs +16 -1
- package/esm2022/ngmodule/http-adapter.mjs +62 -16
- package/esm2022/ngmodule/nauth.module.mjs +4 -1
- package/esm2022/standalone/auth.guard.mjs +32 -13
- package/esm2022/standalone/auth.service.mjs +16 -1
- package/esm2022/standalone/http-adapter.mjs +62 -16
- package/esm2022/standalone/social-redirect-callback.guard.mjs +28 -24
- package/fesm2022/nauth-toolkit-client-angular-standalone.mjs +131 -49
- package/fesm2022/nauth-toolkit-client-angular-standalone.mjs.map +1 -1
- package/fesm2022/nauth-toolkit-client-angular.mjs +218 -118
- package/fesm2022/nauth-toolkit-client-angular.mjs.map +1 -1
- package/lib/auth.guard.d.ts +39 -16
- package/lib/social-redirect-callback.guard.d.ts +2 -2
- package/ngmodule/auth.service.d.ts +13 -0
- package/ngmodule/http-adapter.d.ts +16 -0
- package/package.json +2 -2
- package/standalone/auth.guard.d.ts +13 -6
- package/standalone/auth.service.d.ts +13 -0
- package/standalone/http-adapter.d.ts +16 -0
- package/standalone/social-redirect-callback.guard.d.ts +2 -2
|
@@ -29,6 +29,37 @@ export class AngularHttpAdapter {
|
|
|
29
29
|
constructor(http) {
|
|
30
30
|
this.http = http;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Safely parse a JSON response body.
|
|
34
|
+
*
|
|
35
|
+
* Angular's fetch backend (`withFetch()`) will throw a raw `SyntaxError` if
|
|
36
|
+
* `responseType: 'json'` is used and the backend returns HTML (common for
|
|
37
|
+
* proxies, 502 pages, SSR fallbacks, or misrouted requests).
|
|
38
|
+
*
|
|
39
|
+
* To avoid crashing consumer apps, we always request as text and then parse
|
|
40
|
+
* JSON only when the response actually looks like JSON.
|
|
41
|
+
*
|
|
42
|
+
* @param bodyText - Raw response body as text
|
|
43
|
+
* @param contentType - Content-Type header value (if available)
|
|
44
|
+
* @returns Parsed JSON value (unknown)
|
|
45
|
+
* @throws {SyntaxError} When body is non-empty but not valid JSON
|
|
46
|
+
*/
|
|
47
|
+
parseJsonBody(bodyText, contentType) {
|
|
48
|
+
const trimmed = bodyText.trim();
|
|
49
|
+
if (!trimmed)
|
|
50
|
+
return null;
|
|
51
|
+
// If it's clearly HTML, never attempt JSON.parse (some proxies mislabel Content-Type).
|
|
52
|
+
if (trimmed.startsWith('<')) {
|
|
53
|
+
return bodyText;
|
|
54
|
+
}
|
|
55
|
+
const looksLikeJson = trimmed.startsWith('{') || trimmed.startsWith('[');
|
|
56
|
+
const isJsonContentType = typeof contentType === 'string' && contentType.toLowerCase().includes('application/json');
|
|
57
|
+
if (!looksLikeJson && !isJsonContentType) {
|
|
58
|
+
// Return raw text when it doesn't look like JSON (e.g., HTML error pages).
|
|
59
|
+
return bodyText;
|
|
60
|
+
}
|
|
61
|
+
return JSON.parse(trimmed);
|
|
62
|
+
}
|
|
32
63
|
/**
|
|
33
64
|
* Execute HTTP request using Angular's HttpClient.
|
|
34
65
|
*
|
|
@@ -38,29 +69,40 @@ export class AngularHttpAdapter {
|
|
|
38
69
|
*/
|
|
39
70
|
async request(config) {
|
|
40
71
|
try {
|
|
41
|
-
// Use Angular's HttpClient - goes through ALL interceptors
|
|
42
|
-
|
|
72
|
+
// Use Angular's HttpClient - goes through ALL interceptors.
|
|
73
|
+
// IMPORTANT: Use responseType 'text' to avoid raw JSON.parse crashes when
|
|
74
|
+
// the backend returns HTML (seen in some proxy/SSR/misroute setups).
|
|
75
|
+
const res = await firstValueFrom(this.http.request(config.method, config.url, {
|
|
43
76
|
body: config.body,
|
|
44
77
|
headers: config.headers,
|
|
45
78
|
withCredentials: config.credentials === 'include',
|
|
46
|
-
observe: '
|
|
79
|
+
observe: 'response',
|
|
80
|
+
responseType: 'text',
|
|
47
81
|
}));
|
|
82
|
+
const contentType = res.headers?.get('content-type');
|
|
83
|
+
const parsed = this.parseJsonBody(res.body ?? '', contentType);
|
|
48
84
|
return {
|
|
49
|
-
data,
|
|
50
|
-
status:
|
|
51
|
-
headers: {}, //
|
|
85
|
+
data: parsed,
|
|
86
|
+
status: res.status,
|
|
87
|
+
headers: {}, // Reserved for future header passthrough if needed
|
|
52
88
|
};
|
|
53
89
|
}
|
|
54
90
|
catch (error) {
|
|
55
91
|
if (error instanceof HttpErrorResponse) {
|
|
56
|
-
// Convert Angular's HttpErrorResponse to NAuthClientError
|
|
57
|
-
|
|
58
|
-
const
|
|
92
|
+
// Convert Angular's HttpErrorResponse to NAuthClientError.
|
|
93
|
+
// When using responseType 'text', `error.error` is typically a string.
|
|
94
|
+
const contentType = error.headers?.get('content-type') ?? null;
|
|
95
|
+
const rawBody = typeof error.error === 'string' ? error.error : '';
|
|
96
|
+
const parsedError = this.parseJsonBody(rawBody, contentType);
|
|
97
|
+
const errorData = typeof parsedError === 'object' && parsedError !== null ? parsedError : {};
|
|
98
|
+
const code = typeof errorData['code'] === 'string' ? errorData['code'] : NAuthErrorCode.INTERNAL_ERROR;
|
|
59
99
|
const message = typeof errorData['message'] === 'string'
|
|
60
|
-
? errorData
|
|
61
|
-
:
|
|
62
|
-
|
|
63
|
-
|
|
100
|
+
? errorData['message']
|
|
101
|
+
: typeof parsedError === 'string' && parsedError.trim()
|
|
102
|
+
? parsedError
|
|
103
|
+
: error.message || `Request failed with status ${error.status}`;
|
|
104
|
+
const timestamp = typeof errorData['timestamp'] === 'string' ? errorData['timestamp'] : undefined;
|
|
105
|
+
const details = typeof errorData['details'] === 'object' ? errorData['details'] : undefined;
|
|
64
106
|
throw new NAuthClientError(code, message, {
|
|
65
107
|
statusCode: error.status,
|
|
66
108
|
timestamp,
|
|
@@ -68,8 +110,12 @@ export class AngularHttpAdapter {
|
|
|
68
110
|
isNetworkError: error.status === 0, // Network error (no response from server)
|
|
69
111
|
});
|
|
70
112
|
}
|
|
71
|
-
// Re-throw non-HTTP errors
|
|
72
|
-
|
|
113
|
+
// Re-throw non-HTTP errors as an SDK error so consumers don't see raw parser crashes.
|
|
114
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
115
|
+
throw new NAuthClientError(NAuthErrorCode.INTERNAL_ERROR, message, {
|
|
116
|
+
statusCode: 0,
|
|
117
|
+
isNetworkError: true,
|
|
118
|
+
});
|
|
73
119
|
}
|
|
74
120
|
}
|
|
75
121
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: AngularHttpAdapter, deps: [{ token: i1.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
@@ -78,4 +124,4 @@ export class AngularHttpAdapter {
|
|
|
78
124
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: AngularHttpAdapter, decorators: [{
|
|
79
125
|
type: Injectable
|
|
80
126
|
}], ctorParameters: () => [{ type: i1.HttpClient }] });
|
|
81
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaHR0cC1hZGFwdGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3RhbmRhbG9uZS9odHRwLWFkYXB0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUMzQyxPQUFPLEVBQWMsaUJBQWlCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUNyRSxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQ3RDLE9BQU8sRUFBMEMsZ0JBQWdCLEVBQUUsY0FBYyxFQUFFLE1BQU0sdUJBQXVCLENBQUM7OztBQUVqSDs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW1CRztBQUVILE1BQU0sT0FBTyxrQkFBa0I7SUFDQTtJQUE3QixZQUE2QixJQUFnQjtRQUFoQixTQUFJLEdBQUosSUFBSSxDQUFZO0lBQUcsQ0FBQztJQUVqRDs7Ozs7O09BTUc7SUFDSCxLQUFLLENBQUMsT0FBTyxDQUFJLE1BQW1CO1FBQ2xDLElBQUksQ0FBQztZQUNILDJEQUEyRDtZQUMzRCxNQUFNLElBQUksR0FBRyxNQUFNLGNBQWMsQ0FDL0IsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUksTUFBTSxDQUFDLE1BQU0sRUFBRSxNQUFNLENBQUMsR0FBRyxFQUFFO2dCQUM5QyxJQUFJLEVBQUUsTUFBTSxDQUFDLElBQUk7Z0JBQ2pCLE9BQU8sRUFBRSxNQUFNLENBQUMsT0FBTztnQkFDdkIsZUFBZSxFQUFFLE1BQU0sQ0FBQyxXQUFXLEtBQUssU0FBUztnQkFDakQsT0FBTyxFQUFFLE1BQU0sRUFBRSx3QkFBd0I7YUFDMUMsQ0FBQyxDQUNILENBQUM7WUFFRixPQUFPO2dCQUNMLElBQUk7Z0JBQ0osTUFBTSxFQUFFLEdBQUcsRUFBRSwwQ0FBMEM7Z0JBQ3ZELE9BQU8sRUFBRSxFQUFFLEVBQUUsaURBQWlEO2FBQy9ELENBQUM7UUFDSixDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLElBQUksS0FBSyxZQUFZLGlCQUFpQixFQUFFLENBQUM7Z0JBQ3ZDLDBEQUEwRDtnQkFDMUQsTUFBTSxTQUFTLEdBQUcsS0FBSyxDQUFDLEtBQUssSUFBSSxFQUFFLENBQUM7Z0JBQ3BDLE1BQU0sSUFBSSxHQUNSLE9BQU8sU0FBUyxDQUFDLE1BQU0sQ0FBQyxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUUsU0FBUyxDQUFDLElBQXVCLENBQUMsQ0FBQyxDQUFDLGNBQWMsQ0FBQyxjQUFjLENBQUM7Z0JBQzdHLE1BQU0sT0FBTyxHQUNYLE9BQU8sU0FBUyxDQUFDLFNBQVMsQ0FBQyxLQUFLLFFBQVE7b0JBQ3RDLENBQUMsQ0FBRSxTQUFTLENBQUMsT0FBa0I7b0JBQy9CLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxJQUFJLDhCQUE4QixLQUFLLENBQUMsTUFBTSxFQUFFLENBQUM7Z0JBQ3BFLE1BQU0sU0FBUyxHQUFHLE9BQU8sU0FBUyxDQUFDLFdBQVcsQ0FBQyxLQUFLLFFBQVEsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDO2dCQUMvRixNQUFNLE9BQU8sR0FBRyxTQUFTLENBQUMsU0FBUyxDQUF3QyxDQUFDO2dCQUU1RSxNQUFNLElBQUksZ0JBQWdCLENBQUMsSUFBSSxFQUFFLE9BQU8sRUFBRTtvQkFDeEMsVUFBVSxFQUFFLEtBQUssQ0FBQyxNQUFNO29CQUN4QixTQUFTO29CQUNULE9BQU87b0JBQ1AsY0FBYyxFQUFFLEtBQUssQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLDBDQUEwQztpQkFDL0UsQ0FBQyxDQUFDO1lBQ0wsQ0FBQztZQUVELDJCQUEyQjtZQUMzQixNQUFNLEtBQUssQ0FBQztRQUNkLENBQUM7SUFDSCxDQUFDO3dHQW5EVSxrQkFBa0I7NEdBQWxCLGtCQUFrQjs7NEZBQWxCLGtCQUFrQjtrQkFEOUIsVUFBVSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdGFibGUgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IEh0dHBDbGllbnQsIEh0dHBFcnJvclJlc3BvbnNlIH0gZnJvbSAnQGFuZ3VsYXIvY29tbW9uL2h0dHAnO1xuaW1wb3J0IHsgZmlyc3RWYWx1ZUZyb20gfSBmcm9tICdyeGpzJztcbmltcG9ydCB7IEh0dHBBZGFwdGVyLCBIdHRwUmVxdWVzdCwgSHR0cFJlc3BvbnNlLCBOQXV0aENsaWVudEVycm9yLCBOQXV0aEVycm9yQ29kZSB9IGZyb20gJ0BuYXV0aC10b29sa2l0L2NsaWVudCc7XG5cbi8qKlxuICogSFRUUCBhZGFwdGVyIGZvciBBbmd1bGFyIHVzaW5nIEh0dHBDbGllbnQuXG4gKlxuICogVGhpcyBhZGFwdGVyOlxuICogLSBVc2VzIEFuZ3VsYXIncyBIdHRwQ2xpZW50IGZvciBhbGwgcmVxdWVzdHNcbiAqIC0gV29ya3Mgd2l0aCBBbmd1bGFyJ3MgSFRUUCBpbnRlcmNlcHRvcnMgKGluY2x1ZGluZyBhdXRoSW50ZXJjZXB0b3IpXG4gKiAtIEF1dG8tcHJvdmlkZWQgdmlhIEFuZ3VsYXIgREkgKHByb3ZpZGVkSW46ICdyb290JylcbiAqIC0gQ29udmVydHMgSHR0cENsaWVudCByZXNwb25zZXMgdG8gSHR0cFJlc3BvbnNlIGZvcm1hdFxuICogLSBDb252ZXJ0cyBIdHRwRXJyb3JSZXNwb25zZSB0byBOQXV0aENsaWVudEVycm9yXG4gKlxuICogVXNlcnMgZG9uJ3QgbmVlZCB0byBjb25maWd1cmUgdGhpcyBtYW51YWxseSAtIGl0J3MgYXV0b21hdGljYWxseVxuICogaW5qZWN0ZWQgd2hlbiB1c2luZyBBdXRoU2VydmljZSBpbiBBbmd1bGFyIGFwcHMuXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIC8vIEF1dG9tYXRpYyB1c2FnZSAobm8gbWFudWFsIHNldHVwIG5lZWRlZClcbiAqIC8vIEF1dGhTZXJ2aWNlIGF1dG9tYXRpY2FsbHkgaW5qZWN0cyBBbmd1bGFySHR0cEFkYXB0ZXJcbiAqIGNvbnN0cnVjdG9yKHByaXZhdGUgYXV0aDogQXV0aFNlcnZpY2UpIHt9XG4gKiBgYGBcbiAqL1xuQEluamVjdGFibGUoKVxuZXhwb3J0IGNsYXNzIEFuZ3VsYXJIdHRwQWRhcHRlciBpbXBsZW1lbnRzIEh0dHBBZGFwdGVyIHtcbiAgY29uc3RydWN0b3IocHJpdmF0ZSByZWFkb25seSBodHRwOiBIdHRwQ2xpZW50KSB7fVxuXG4gIC8qKlxuICAgKiBFeGVjdXRlIEhUVFAgcmVxdWVzdCB1c2luZyBBbmd1bGFyJ3MgSHR0cENsaWVudC5cbiAgICpcbiAgICogQHBhcmFtIGNvbmZpZyAtIFJlcXVlc3QgY29uZmlndXJhdGlvblxuICAgKiBAcmV0dXJucyBSZXNwb25zZSB3aXRoIHBhcnNlZCBkYXRhXG4gICAqIEB0aHJvd3MgTkF1dGhDbGllbnRFcnJvciBpZiByZXF1ZXN0IGZhaWxzXG4gICAqL1xuICBhc3luYyByZXF1ZXN0PFQ+KGNvbmZpZzogSHR0cFJlcXVlc3QpOiBQcm9taXNlPEh0dHBSZXNwb25zZTxUPj4ge1xuICAgIHRyeSB7XG4gICAgICAvLyBVc2UgQW5ndWxhcidzIEh0dHBDbGllbnQgLSBnb2VzIHRocm91Z2ggQUxMIGludGVyY2VwdG9yc1xuICAgICAgY29uc3QgZGF0YSA9IGF3YWl0IGZpcnN0VmFsdWVGcm9tKFxuICAgICAgICB0aGlzLmh0dHAucmVxdWVzdDxUPihjb25maWcubWV0aG9kLCBjb25maWcudXJsLCB7XG4gICAgICAgICAgYm9keTogY29uZmlnLmJvZHksXG4gICAgICAgICAgaGVhZGVyczogY29uZmlnLmhlYWRlcnMsXG4gICAgICAgICAgd2l0aENyZWRlbnRpYWxzOiBjb25maWcuY3JlZGVudGlhbHMgPT09ICdpbmNsdWRlJyxcbiAgICAgICAgICBvYnNlcnZlOiAnYm9keScsIC8vIE9ubHkgcmV0dXJuIGJvZHkgZGF0YVxuICAgICAgICB9KSxcbiAgICAgICk7XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIGRhdGEsXG4gICAgICAgIHN0YXR1czogMjAwLCAvLyBIdHRwQ2xpZW50IG9ubHkgcmV0dXJucyBkYXRhIG9uIHN1Y2Nlc3NcbiAgICAgICAgaGVhZGVyczoge30sIC8vIENhbiBleHRyYWN0IGZyb20gb2JzZXJ2ZTogJ3Jlc3BvbnNlJyBpZiBuZWVkZWRcbiAgICAgIH07XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGlmIChlcnJvciBpbnN0YW5jZW9mIEh0dHBFcnJvclJlc3BvbnNlKSB7XG4gICAgICAgIC8vIENvbnZlcnQgQW5ndWxhcidzIEh0dHBFcnJvclJlc3BvbnNlIHRvIE5BdXRoQ2xpZW50RXJyb3JcbiAgICAgICAgY29uc3QgZXJyb3JEYXRhID0gZXJyb3IuZXJyb3IgfHwge307XG4gICAgICAgIGNvbnN0IGNvZGUgPVxuICAgICAgICAgIHR5cGVvZiBlcnJvckRhdGFbJ2NvZGUnXSA9PT0gJ3N0cmluZycgPyAoZXJyb3JEYXRhLmNvZGUgYXMgTkF1dGhFcnJvckNvZGUpIDogTkF1dGhFcnJvckNvZGUuSU5URVJOQUxfRVJST1I7XG4gICAgICAgIGNvbnN0IG1lc3NhZ2UgPVxuICAgICAgICAgIHR5cGVvZiBlcnJvckRhdGFbJ21lc3NhZ2UnXSA9PT0gJ3N0cmluZydcbiAgICAgICAgICAgID8gKGVycm9yRGF0YS5tZXNzYWdlIGFzIHN0cmluZylcbiAgICAgICAgICAgIDogZXJyb3IubWVzc2FnZSB8fCBgUmVxdWVzdCBmYWlsZWQgd2l0aCBzdGF0dXMgJHtlcnJvci5zdGF0dXN9YDtcbiAgICAgICAgY29uc3QgdGltZXN0YW1wID0gdHlwZW9mIGVycm9yRGF0YVsndGltZXN0YW1wJ10gPT09ICdzdHJpbmcnID8gZXJyb3JEYXRhLnRpbWVzdGFtcCA6IHVuZGVmaW5lZDtcbiAgICAgICAgY29uc3QgZGV0YWlscyA9IGVycm9yRGF0YVsnZGV0YWlscyddIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+IHwgdW5kZWZpbmVkO1xuXG4gICAgICAgIHRocm93IG5ldyBOQXV0aENsaWVudEVycm9yKGNvZGUsIG1lc3NhZ2UsIHtcbiAgICAgICAgICBzdGF0dXNDb2RlOiBlcnJvci5zdGF0dXMsXG4gICAgICAgICAgdGltZXN0YW1wLFxuICAgICAgICAgIGRldGFpbHMsXG4gICAgICAgICAgaXNOZXR3b3JrRXJyb3I6IGVycm9yLnN0YXR1cyA9PT0gMCwgLy8gTmV0d29yayBlcnJvciAobm8gcmVzcG9uc2UgZnJvbSBzZXJ2ZXIpXG4gICAgICAgIH0pO1xuICAgICAgfVxuXG4gICAgICAvLyBSZS10aHJvdyBub24tSFRUUCBlcnJvcnNcbiAgICAgIHRocm93IGVycm9yO1xuICAgIH1cbiAgfVxufVxuIl19
|
|
127
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaHR0cC1hZGFwdGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3RhbmRhbG9uZS9odHRwLWFkYXB0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUMzQyxPQUFPLEVBQWMsaUJBQWlCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUNyRSxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQ3RDLE9BQU8sRUFBMEMsZ0JBQWdCLEVBQUUsY0FBYyxFQUFFLE1BQU0sdUJBQXVCLENBQUM7OztBQUVqSDs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW1CRztBQUVILE1BQU0sT0FBTyxrQkFBa0I7SUFDQTtJQUE3QixZQUE2QixJQUFnQjtRQUFoQixTQUFJLEdBQUosSUFBSSxDQUFZO0lBQUcsQ0FBQztJQUVqRDs7Ozs7Ozs7Ozs7Ozs7T0FjRztJQUNLLGFBQWEsQ0FBQyxRQUFnQixFQUFFLFdBQTBCO1FBQ2hFLE1BQU0sT0FBTyxHQUFHLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUNoQyxJQUFJLENBQUMsT0FBTztZQUFFLE9BQU8sSUFBSSxDQUFDO1FBRTFCLHVGQUF1RjtRQUN2RixJQUFJLE9BQU8sQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztZQUM1QixPQUFPLFFBQVEsQ0FBQztRQUNsQixDQUFDO1FBRUQsTUFBTSxhQUFhLEdBQUcsT0FBTyxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsSUFBSSxPQUFPLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3pFLE1BQU0saUJBQWlCLEdBQUcsT0FBTyxXQUFXLEtBQUssUUFBUSxJQUFJLFdBQVcsQ0FBQyxXQUFXLEVBQUUsQ0FBQyxRQUFRLENBQUMsa0JBQWtCLENBQUMsQ0FBQztRQUVwSCxJQUFJLENBQUMsYUFBYSxJQUFJLENBQUMsaUJBQWlCLEVBQUUsQ0FBQztZ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LENBQUM7Z0JBRXpILE1BQU0sSUFBSSxnQkFBZ0IsQ0FBQyxJQUFJLEVBQUUsT0FBTyxFQUFFO29CQUN4QyxVQUFVLEVBQUUsS0FBSyxDQUFDLE1BQU07b0JBQ3hCLFNBQVM7b0JBQ1QsT0FBTztvQkFDUCxjQUFjLEVBQUUsS0FBSyxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsMENBQTBDO2lCQUMvRSxDQUFDLENBQUM7WUFDTCxDQUFDO1lBRUQsc0ZBQXNGO1lBQ3RGLE1BQU0sT0FBTyxHQUFHLEtBQUssWUFBWSxLQUFLLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLGVBQWUsQ0FBQztZQUN6RSxNQUFNLElBQUksZ0JBQWdCLENBQUMsY0FBYyxDQUFDLGNBQWMsRUFBRSxPQUFPLEVBQUU7Z0JBQ2pFLFVBQVUsRUFBRSxDQUFDO2dCQUNiLGNBQWMsRUFBRSxJQUFJO2FBQ3JCLENBQUMsQ0FBQztRQUNMLENBQUM7SUFDSCxDQUFDO3dHQXRHVSxrQkFBa0I7NEdBQWxCLGtCQUFrQjs7NEZBQWxCLGtCQUFrQjtrQkFEOUIsVUFBVSIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdGFibGUgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IEh0dHBDbGllbnQsIEh0dHBFcnJvclJlc3BvbnNlIH0gZnJvbSAnQGFuZ3VsYXIvY29tbW9uL2h0dHAnO1xuaW1wb3J0IHsgZmlyc3RWYWx1ZUZyb20gfSBmcm9tICdyeGpzJztcbmltcG9ydCB7IEh0dHBBZGFwdGVyLCBIdHRwUmVxdWVzdCwgSHR0cFJlc3BvbnNlLCBOQXV0aENsaWVudEVycm9yLCBOQXV0aEVycm9yQ29kZSB9IGZyb20gJ0BuYXV0aC10b29sa2l0L2NsaWVudCc7XG5cbi8qKlxuICogSFRUUCBhZGFwdGVyIGZvciBBbmd1bGFyIHVzaW5nIEh0dHBDbGllbnQuXG4gKlxuICogVGhpcyBhZGFwdGVyOlxuICogLSBVc2VzIEFuZ3VsYXIncyBIdHRwQ2xpZW50IGZvciBhbGwgcmVxdWVzdHNcbiAqIC0gV29ya3Mgd2l0aCBBbmd1bGFyJ3MgSFRUUCBpbnRlcmNlcHRvcnMgKGluY2x1ZGluZyBhdXRoSW50ZXJjZXB0b3IpXG4gKiAtIEF1dG8tcHJvdmlkZWQgdmlhIEFuZ3VsYXIgREkgKHByb3ZpZGVkSW46ICdyb290JylcbiAqIC0gQ29udmVydHMgSHR0cENsaWVudCByZXNwb25zZXMgdG8gSHR0cFJlc3BvbnNlIGZvcm1hdFxuICogLSBDb252ZXJ0cyBIdHRwRXJyb3JSZXNwb25zZSB0byBOQXV0aENsaWVudEVycm9yXG4gKlxuICogVXNlcnMgZG9uJ3QgbmVlZCB0byBjb25maWd1cmUgdGhpcyBtYW51YWxseSAtIGl0J3MgYXV0b21hdGljYWxseVxuICogaW5qZWN0ZWQgd2hlbiB1c2luZyBBdXRoU2VydmljZSBpbiBBbmd1bGFyIGFwcHMuXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIC8vIEF1dG9tYXRpYyB1c2FnZSAobm8gbWFudWFsIHNldHVwIG5lZWRlZClcbiAqIC8vIEF1dGhTZXJ2aWNlIGF1dG9tYXRpY2FsbHkgaW5qZWN0cyBBbmd1bGFySHR0cEFkYXB0ZXJcbiAqIGNvbnN0cnVjdG9yKHByaXZhdGUgYXV0aDogQXV0aFNlcnZpY2UpIHt9XG4gKiBgYGBcbiAqL1xuQEluamVjdGFibGUoKVxuZXhwb3J0IGNsYXNzIEFuZ3VsYXJIdHRwQWRhcHRlciBpbXBsZW1lbnRzIEh0dHBBZGFwdGVyIHtcbiAgY29uc3RydWN0b3IocHJpdmF0ZSByZWFkb25seSBodHRwOiBIdHRwQ2xpZW50KSB7fVxuXG4gIC8qKlxuICAgKiBTYWZlbHkgcGFyc2UgYSBKU09OIHJlc3BvbnNlIGJvZHkuXG4gICAqXG4gICAqIEFuZ3VsYXIncyBmZXRjaCBiYWNrZW5kIChgd2l0aEZldGNoKClgKSB3aWxsIHRocm93IGEgcmF3IGBTeW50YXhFcnJvcmAgaWZcbiAgICogYHJlc3BvbnNlVHlwZTogJ2pzb24nYCBpcyB1c2VkIGFuZCB0aGUgYmFja2VuZCByZXR1cm5zIEhUTUwgKGNvbW1vbiBmb3JcbiAgICogcHJveGllcywgNTAyIHBhZ2VzLCBTU1IgZmFsbGJhY2tzLCBvciBtaXNyb3V0ZWQgcmVxdWVzdHMpLlxuICAgKlxuICAgKiBUbyBhdm9pZCBjcmFzaGluZyBjb25zdW1lciBhcHBzLCB3ZSBhbHdheXMgcmVxdWVzdCBhcyB0ZXh0IGFuZCB0aGVuIHBhcnNlXG4gICAqIEpTT04gb25seSB3aGVuIHRoZSByZXNwb25zZSBhY3R1YWxseSBsb29rcyBsaWtlIEpTT04uXG4gICAqXG4gICAqIEBwYXJhbSBib2R5VGV4dCAtIFJhdyByZXNwb25zZSBib2R5IGFzIHRleHRcbiAgICogQHBhcmFtIGNvbnRlbnRUeXBlIC0gQ29udGVudC1UeXBlIGhlYWRlciB2YWx1ZSAoaWYgYXZhaWxhYmxlKVxuICAgKiBAcmV0dXJucyBQYXJzZWQgSlNPTiB2YWx1ZSAodW5rbm93bilcbiAgICogQHRocm93cyB7U3ludGF4RXJyb3J9IFdoZW4gYm9keSBpcyBub24tZW1wdHkgYnV0IG5vdCB2YWxpZCBKU09OXG4gICAqL1xuICBwcml2YXRlIHBhcnNlSnNvbkJvZHkoYm9keVRleHQ6IHN0cmluZywgY29udGVudFR5cGU6IHN0cmluZyB8IG51bGwpOiB1bmtub3duIHtcbiAgICBjb25zdCB0cmltbWVkID0gYm9keVRleHQudHJpbSgpO1xuICAgIGlmICghdHJpbW1lZCkgcmV0dXJuIG51bGw7XG5cbiAgICAvLyBJZiBpdCdzIGNsZWFybHkgSFRNTCwgbmV2ZXIgYXR0ZW1wdCBKU09OLnBhcnNlIChzb21lIHByb3hpZXMgbWlzbGFiZWwgQ29udGVudC1UeXBlKS5cbiAgICBpZiAodHJpbW1lZC5zdGFydHNXaXRoKCc8JykpIHtcbiAgICAgIHJldHVybiBib2R5VGV4dDtcbiAgICB9XG5cbiAgICBjb25zdCBsb29rc0xpa2VKc29uID0gdHJpbW1lZC5zdGFydHNXaXRoKCd7JykgfHwgdHJpbW1lZC5zdGFydHNXaXRoKCdbJyk7XG4gICAgY29uc3QgaXNKc29uQ29udGVudFR5cGUgPSB0eXBlb2YgY29udGVudFR5cGUgPT09ICdzdHJpbmcnICYmIGNvbnRlbnRUeXBlLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMoJ2FwcGxpY2F0aW9uL2pzb24nKTtcblxuICAgIGlmICghbG9va3NMaWtlSnNvbiAmJiAhaXNKc29uQ29udGVudFR5cGUpIHtcbiAgICAgIC8vIFJldHVybiByYXcgdGV4dCB3aGVuIGl0IGRvZXNuJ3QgbG9vayBsaWtlIEpTT04gKGUuZy4sIEhUTUwgZXJyb3IgcGFnZXMpLlxuICAgICAgcmV0dXJuIGJvZHlUZXh0O1xuICAgIH1cblxuICAgIHJldHVybiBKU09OLnBhcnNlKHRyaW1tZWQpIGFzIHVua25vd247XG4gIH1cblxuICAvKipcbiAgICogRXhlY3V0ZSBIVFRQIHJlcXVlc3QgdXNpbmcgQW5ndWxhcidzIEh0dHBDbGllbnQuXG4gICAqXG4gICAqIEBwYXJhbSBjb25maWcgLSBSZXF1ZXN0IGNvbmZpZ3VyYXRpb25cbiAgICogQHJldHVybnMgUmVzcG9uc2Ugd2l0aCBwYXJzZWQgZGF0YVxuICAgKiBAdGhyb3dzIE5BdXRoQ2xpZW50RXJyb3IgaWYgcmVxdWVzdCBmYWlsc1xuICAgKi9cbiAgYXN5bmMgcmVxdWVzdDxUPihjb25maWc6IEh0dHBSZXF1ZXN0KTogUHJvbWlzZTxIdHRwUmVzcG9uc2U8VD4+IHtcbiAgICB0cnkge1xuICAgICAgLy8gVXNlIEFuZ3VsYXIncyBIdHRwQ2xpZW50IC0gZ29lcyB0aHJvdWdoIEFMTCBpbnRlcmNlcHRvcnMuXG4gICAgICAvLyBJTVBPUlRBTlQ6IFVzZSByZXNwb25zZVR5cGUgJ3RleHQnIHRvIGF2b2lkIHJhdyBKU09OLnBhcnNlIGNyYXNoZXMgd2hlblxuICAgICAgLy8gdGhlIGJhY2tlbmQgcmV0dXJucyBIVE1MIChzZWVuIGluIHNvbWUgcHJveHkvU1NSL21pc3JvdXRlIHNldHVwcykuXG4gICAgICBjb25zdCByZXMgPSBhd2FpdCBmaXJzdFZhbHVlRnJvbShcbiAgICAgICAgdGhpcy5odHRwLnJlcXVlc3QoY29uZmlnLm1ldGhvZCwgY29uZmlnLnVybCwge1xuICAgICAgICAgIGJvZHk6IGNvbmZpZy5ib2R5LFxuICAgICAgICAgIGhlYWRlcnM6IGNvbmZpZy5oZWFkZXJzLFxuICAgICAgICAgIHdpdGhDcmVkZW50aWFsczogY29uZmlnLmNyZWRlbnRpYWxzID09PSAnaW5jbHVkZScsXG4gICAgICAgICAgb2JzZXJ2ZTogJ3Jlc3BvbnNlJyxcbiAgICAgICAgICByZXNwb25zZVR5cGU6ICd0ZXh0JyxcbiAgICAgICAgfSksXG4gICAgICApO1xuXG4gICAgICBjb25zdCBjb250ZW50VHlwZSA9IHJlcy5oZWFkZXJzPy5nZXQoJ2NvbnRlbnQtdHlwZScpO1xuICAgICAgY29uc3QgcGFyc2VkID0gdGhpcy5wYXJzZUpzb25Cb2R5KHJlcy5ib2R5ID8/ICcnLCBjb250ZW50VHlwZSk7XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIGRhdGE6IHBhcnNlZCBhcyBULFxuICAgICAgICBzdGF0dXM6IHJlcy5zdGF0dXMsXG4gICAgICAgIGhlYWRlcnM6IHt9LCAvLyBSZXNlcnZlZCBmb3IgZnV0dXJlIGhlYWRlciBwYXNzdGhyb3VnaCBpZiBuZWVkZWRcbiAgICAgIH07XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGlmIChlcnJvciBpbnN0YW5jZW9mIEh0dHBFcnJvclJlc3BvbnNlKSB7XG4gICAgICAgIC8vIENvbnZlcnQgQW5ndWxhcidzIEh0dHBFcnJvclJlc3BvbnNlIHRvIE5BdXRoQ2xpZW50RXJyb3IuXG4gICAgICAgIC8vIFdoZW4gdXNpbmcgcmVzcG9uc2VUeXBlICd0ZXh0JywgYGVycm9yLmVycm9yYCBpcyB0eXBpY2FsbHkgYSBzdHJpbmcuXG4gICAgICAgIGNvbnN0IGNvbnRlbnRUeXBlID0gZXJyb3IuaGVhZGVycz8uZ2V0KCdjb250ZW50LXR5cGUnKSA/PyBudWxsO1xuICAgICAgICBjb25zdCByYXdCb2R5ID0gdHlwZW9mIGVycm9yLmVycm9yID09PSAnc3RyaW5nJyA/IGVycm9yLmVycm9yIDogJyc7XG4gICAgICAgIGNvbnN0IHBhcnNlZEVycm9yID0gdGhpcy5wYXJzZUpzb25Cb2R5KHJhd0JvZHksIGNvbnRlbnRUeXBlKTtcblxuICAgICAgICBjb25zdCBlcnJvckRhdGEgPSB0eXBlb2YgcGFyc2VkRXJyb3IgPT09ICdvYmplY3QnICYmIHBhcnNlZEVycm9yICE9PSBudWxsID8gKHBhcnNlZEVycm9yIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+KSA6IHt9O1xuICAgICAgICBjb25zdCBjb2RlID0gdHlwZW9mIGVycm9yRGF0YVsnY29kZSddID09PSAnc3RyaW5nJyA/IChlcnJvckRhdGFbJ2NvZGUnXSBhcyBOQXV0aEVycm9yQ29kZSkgOiBOQXV0aEVycm9yQ29kZS5JTlRFUk5BTF9FUlJPUjtcbiAgICAgICAgY29uc3QgbWVzc2FnZSA9XG4gICAgICAgICAgdHlwZW9mIGVycm9yRGF0YVsnbWVzc2FnZSddID09PSAnc3RyaW5nJ1xuICAgICAgICAgICAgPyAoZXJyb3JEYXRhWydtZXNzYWdlJ10gYXMgc3RyaW5nKVxuICAgICAgICAgICAgOiB0eXBlb2YgcGFyc2VkRXJyb3IgPT09ICdzdHJpbmcnICYmIHBhcnNlZEVycm9yLnRyaW0oKVxuICAgICAgICAgICAgICA/IHBhcnNlZEVycm9yXG4gICAgICAgICAgICAgIDogZXJyb3IubWVzc2FnZSB8fCBgUmVxdWVzdCBmYWlsZWQgd2l0aCBzdGF0dXMgJHtlcnJvci5zdGF0dXN9YDtcbiAgICAgICAgY29uc3QgdGltZXN0YW1wID0gdHlwZW9mIGVycm9yRGF0YVsndGltZXN0YW1wJ10gPT09ICdzdHJpbmcnID8gKGVycm9yRGF0YVsndGltZXN0YW1wJ10gYXMgc3RyaW5nKSA6IHVuZGVmaW5lZDtcbiAgICAgICAgY29uc3QgZGV0YWlscyA9IHR5cGVvZiBlcnJvckRhdGFbJ2RldGFpbHMnXSA9PT0gJ29iamVjdCcgPyAoZXJyb3JEYXRhWydkZXRhaWxzJ10gYXMgUmVjb3JkPHN0cmluZywgdW5rbm93bj4pIDogdW5kZWZpbmVkO1xuXG4gICAgICAgIHRocm93IG5ldyBOQXV0aENsaWVudEVycm9yKGNvZGUsIG1lc3NhZ2UsIHtcbiAgICAgICAgICBzdGF0dXNDb2RlOiBlcnJvci5zdGF0dXMsXG4gICAgICAgICAgdGltZXN0YW1wLFxuICAgICAgICAgIGRldGFpbHMsXG4gICAgICAgICAgaXNOZXR3b3JrRXJyb3I6IGVycm9yLnN0YXR1cyA9PT0gMCwgLy8gTmV0d29yayBlcnJvciAobm8gcmVzcG9uc2UgZnJvbSBzZXJ2ZXIpXG4gICAgICAgIH0pO1xuICAgICAgfVxuXG4gICAgICAvLyBSZS10aHJvdyBub24tSFRUUCBlcnJvcnMgYXMgYW4gU0RLIGVycm9yIHNvIGNvbnN1bWVycyBkb24ndCBzZWUgcmF3IHBhcnNlciBjcmFzaGVzLlxuICAgICAgY29uc3QgbWVzc2FnZSA9IGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogJ1Vua25vd24gZXJyb3InO1xuICAgICAgdGhyb3cgbmV3IE5BdXRoQ2xpZW50RXJyb3IoTkF1dGhFcnJvckNvZGUuSU5URVJOQUxfRVJST1IsIG1lc3NhZ2UsIHtcbiAgICAgICAgc3RhdHVzQ29kZTogMCxcbiAgICAgICAgaXNOZXR3b3JrRXJyb3I6IHRydWUsXG4gICAgICB9KTtcbiAgICB9XG4gIH1cbn1cbiJdfQ==
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { inject, PLATFORM_ID } from '@angular/core';
|
|
2
2
|
import { isPlatformBrowser } from '@angular/common';
|
|
3
3
|
import { AuthService } from './auth.service';
|
|
4
|
-
import {
|
|
4
|
+
import { NAuthClientError, NAuthErrorCode } from '@nauth-toolkit/client';
|
|
5
5
|
/**
|
|
6
6
|
* Social redirect callback route guard.
|
|
7
7
|
*
|
|
@@ -12,8 +12,8 @@ import { NAUTH_CLIENT_CONFIG } from './tokens';
|
|
|
12
12
|
* - `error` / `error_description` (provider errors)
|
|
13
13
|
*
|
|
14
14
|
* Behavior:
|
|
15
|
-
* - If `exchangeToken` exists: exchanges it via backend
|
|
16
|
-
* - If no `exchangeToken`: treat as cookie-success path
|
|
15
|
+
* - If `exchangeToken` exists: exchanges it via backend (SDK handles navigation automatically).
|
|
16
|
+
* - If no `exchangeToken`: treat as cookie-success path (SDK handles navigation automatically).
|
|
17
17
|
* - If `error` exists: redirects to oauthError route.
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
@@ -27,7 +27,6 @@ import { NAUTH_CLIENT_CONFIG } from './tokens';
|
|
|
27
27
|
*/
|
|
28
28
|
export const socialRedirectCallbackGuard = async () => {
|
|
29
29
|
const auth = inject(AuthService);
|
|
30
|
-
const config = inject(NAUTH_CLIENT_CONFIG);
|
|
31
30
|
const platformId = inject(PLATFORM_ID);
|
|
32
31
|
const isBrowser = isPlatformBrowser(platformId);
|
|
33
32
|
if (!isBrowser) {
|
|
@@ -36,13 +35,13 @@ export const socialRedirectCallbackGuard = async () => {
|
|
|
36
35
|
const params = new URLSearchParams(window.location.search);
|
|
37
36
|
const error = params.get('error');
|
|
38
37
|
const exchangeToken = params.get('exchangeToken');
|
|
38
|
+
const router = auth.getChallengeRouter();
|
|
39
39
|
// Provider error: redirect to oauthError
|
|
40
40
|
if (error) {
|
|
41
|
-
|
|
42
|
-
window.location.replace(errorUrl);
|
|
41
|
+
await router.navigateToError('oauth');
|
|
43
42
|
return false;
|
|
44
43
|
}
|
|
45
|
-
// No exchangeToken: cookie success path;
|
|
44
|
+
// No exchangeToken: cookie success path; hydrate then navigate to success.
|
|
46
45
|
//
|
|
47
46
|
// Note: we do not "activate" the callback route to avoid consumers needing to render a page.
|
|
48
47
|
if (!exchangeToken) {
|
|
@@ -56,26 +55,31 @@ export const socialRedirectCallbackGuard = async () => {
|
|
|
56
55
|
// `currentUser` is still null even though cookies were set successfully.
|
|
57
56
|
try {
|
|
58
57
|
await auth.getProfile();
|
|
58
|
+
await router.navigateToSuccess();
|
|
59
59
|
}
|
|
60
|
-
catch {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
catch (err) {
|
|
61
|
+
// Only treat auth failures (401/403) as OAuth errors
|
|
62
|
+
// Network errors or other issues might be temporary - still try success route
|
|
63
|
+
const isAuthError = err instanceof NAuthClientError &&
|
|
64
|
+
(err.statusCode === 401 ||
|
|
65
|
+
err.statusCode === 403 ||
|
|
66
|
+
err.code === NAuthErrorCode.AUTH_TOKEN_INVALID ||
|
|
67
|
+
err.code === NAuthErrorCode.AUTH_SESSION_EXPIRED ||
|
|
68
|
+
err.code === NAuthErrorCode.AUTH_SESSION_NOT_FOUND);
|
|
69
|
+
if (isAuthError) {
|
|
70
|
+
// Cookies weren't set properly - OAuth failed
|
|
71
|
+
await router.navigateToError('oauth');
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// For network errors or other issues, proceed to success route
|
|
75
|
+
// The auth guard will handle authentication state on the next route
|
|
76
|
+
await router.navigateToSuccess();
|
|
77
|
+
}
|
|
64
78
|
}
|
|
65
|
-
const successUrl = config.redirects?.success || '/';
|
|
66
|
-
window.location.replace(successUrl);
|
|
67
79
|
return false;
|
|
68
80
|
}
|
|
69
|
-
// Exchange token
|
|
70
|
-
|
|
71
|
-
if (response.challengeName) {
|
|
72
|
-
const challengeBase = config.redirects?.challengeBase || '/auth/challenge';
|
|
73
|
-
const challengeRoute = response.challengeName.toLowerCase().replace(/_/g, '-');
|
|
74
|
-
window.location.replace(`${challengeBase}/${challengeRoute}`);
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
const successUrl = config.redirects?.success || '/';
|
|
78
|
-
window.location.replace(successUrl);
|
|
81
|
+
// Exchange token - SDK handles navigation automatically
|
|
82
|
+
await auth.exchangeSocialRedirect(exchangeToken);
|
|
79
83
|
return false;
|
|
80
84
|
};
|
|
81
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
85
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic29jaWFsLXJlZGlyZWN0LWNhbGxiYWNrLmd1YXJkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3RhbmRhbG9uZS9zb2NpYWwtcmVkaXJlY3QtY2FsbGJhY2suZ3VhcmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sRUFBRSxXQUFXLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDcEQsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFFcEQsT0FBTyxFQUFFLFdBQVcsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBRTdDLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxjQUFjLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUV6RTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQXNCRztBQUNILE1BQU0sQ0FBQyxNQUFNLDJCQUEyQixHQUFrQixLQUFLLElBQXNCLEVBQUU7SUFDckYsTUFBTSxJQUFJLEdBQUcsTUFBTSxDQUFDLFdBQVcsQ0FBQyxDQUFDO0lBQ2pDLE1BQU0sVUFBVSxHQUFHLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQztJQUN2QyxNQUFNLFNBQVMsR0FBRyxpQkFBaUIsQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUVoRCxJQUFJLENBQUMsU0FBUyxFQUFFLENBQUM7UUFDZixPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFFRCxNQUFNLE1BQU0sR0FBRyxJQUFJLGVBQWUsQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQzNELE1BQU0sS0FBSyxHQUFHLE1BQU0sQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDbEMsTUFBTSxhQUFhLEdBQUcsTUFBTSxDQUFDLEdBQUcsQ0FBQyxlQUFlLENBQUMsQ0FBQztJQUNsRCxNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztJQUV6Qyx5Q0FBeUM7SUFDekMsSUFBSSxLQUFLLEVBQUUsQ0FBQztRQUNWLE1BQU0sTUFBTSxDQUFDLGVBQWUsQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUN0QyxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFFRCwyRUFBMkU7SUFDM0UsRUFBRTtJQUNGLDZGQUE2RjtJQUM3RixJQUFJLENBQUMsYUFBYSxFQUFFLENBQUM7UUFDbkIsK0VBQStFO1FBQy9FLHNEQUFzRDtRQUN0RCwrRUFBK0U7UUFDL0UsK0ZBQStGO1FBQy9GLG1GQUFtRjtRQUNuRixFQUFFO1FBQ0YscUZBQXFGO1FBQ3JGLHlFQUF5RTtRQUN6RSxJQUFJLENBQUM7WUFDSCxNQUFNLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQztZQUN4QixNQUFNLE1BQU0sQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO1FBQ25DLENBQUM7UUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO1lBQ2IscURBQXFEO1lBQ3JELDhFQUE4RTtZQUM5RSxNQUFNLFdBQVcsR0FDZixHQUFHLFlBQVksZ0JBQWdCO2dCQUMvQixDQUFDLEdBQUcsQ0FBQyxVQUFVLEtBQUssR0FBRztvQkFDckIsR0FBRyxDQUFDLFVBQVUsS0FBSyxHQUFHO29CQUN0QixHQUFHLENBQUMsSUFBSSxLQUFLLGNBQWMsQ0FBQyxrQkFBa0I7b0JBQzlDLEdBQUcsQ0FBQyxJQUFJLEtBQUssY0FBYyxDQUFDLG9CQUFvQjtvQkFDaEQsR0FBRyxDQUFDLElBQUksS0FBSyxjQUFjLENBQUMsc0JBQXNCLENBQUMsQ0FBQztZQUV4RCxJQUFJLFdBQVcsRUFBRSxDQUFDO2dCQUNoQiw4Q0FBOEM7Z0JBQzlDLE1BQU0sTUFBTSxDQUFDLGVBQWUsQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUN4QyxDQUFDO2lCQUFNLENBQUM7Z0JBQ04sK0RBQStEO2dCQUMvRCxvRUFBb0U7Z0JBQ3BFLE1BQU0sTUFBTSxDQUFDLGlCQUFpQixFQUFFLENBQUM7WUFDbkMsQ0FBQztRQUNILENBQUM7UUFDRCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFFRCx3REFBd0Q7SUFDeEQsTUFBTSxJQUFJLENBQUMsc0JBQXNCLENBQUMsYUFBYSxDQUFDLENBQUM7SUFDakQsT0FBTyxLQUFLLENBQUM7QUFDZixDQUFDLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBpbmplY3QsIFBMQVRGT1JNX0lEIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBpc1BsYXRmb3JtQnJvd3NlciB9IGZyb20gJ0Bhbmd1bGFyL2NvbW1vbic7XG5pbXBvcnQgeyB0eXBlIENhbkFjdGl2YXRlRm4gfSBmcm9tICdAYW5ndWxhci9yb3V0ZXInO1xuaW1wb3J0IHsgQXV0aFNlcnZpY2UgfSBmcm9tICcuL2F1dGguc2VydmljZSc7XG5pbXBvcnQgeyBOQVVUSF9DTElFTlRfQ09ORklHIH0gZnJvbSAnLi90b2tlbnMnO1xuaW1wb3J0IHsgTkF1dGhDbGllbnRFcnJvciwgTkF1dGhFcnJvckNvZGUgfSBmcm9tICdAbmF1dGgtdG9vbGtpdC9jbGllbnQnO1xuXG4vKipcbiAqIFNvY2lhbCByZWRpcmVjdCBjYWxsYmFjayByb3V0ZSBndWFyZC5cbiAqXG4gKiBUaGlzIGd1YXJkIHN1cHBvcnRzIHRoZSByZWRpcmVjdC1maXJzdCBzb2NpYWwgZmxvdyB3aGVyZSB0aGUgYmFja2VuZCByZWRpcmVjdHNcbiAqIGJhY2sgdG8gdGhlIGZyb250ZW5kIHdpdGg6XG4gKiAtIGBhcHBTdGF0ZWAgKGFsd2F5cyBvcHRpb25hbClcbiAqIC0gYGV4Y2hhbmdlVG9rZW5gIChwcmVzZW50IGZvciBqc29uL2h5YnJpZCBmbG93cywgYW5kIGZvciBjb29raWUgZmxvd3MgdGhhdCByZXR1cm4gYSBjaGFsbGVuZ2UpXG4gKiAtIGBlcnJvcmAgLyBgZXJyb3JfZGVzY3JpcHRpb25gIChwcm92aWRlciBlcnJvcnMpXG4gKlxuICogQmVoYXZpb3I6XG4gKiAtIElmIGBleGNoYW5nZVRva2VuYCBleGlzdHM6IGV4Y2hhbmdlcyBpdCB2aWEgYmFja2VuZCAoU0RLIGhhbmRsZXMgbmF2aWdhdGlvbiBhdXRvbWF0aWNhbGx5KS5cbiAqIC0gSWYgbm8gYGV4Y2hhbmdlVG9rZW5gOiB0cmVhdCBhcyBjb29raWUtc3VjY2VzcyBwYXRoIChTREsgaGFuZGxlcyBuYXZpZ2F0aW9uIGF1dG9tYXRpY2FsbHkpLlxuICogLSBJZiBgZXJyb3JgIGV4aXN0czogcmVkaXJlY3RzIHRvIG9hdXRoRXJyb3Igcm91dGUuXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIGltcG9ydCB7IHNvY2lhbFJlZGlyZWN0Q2FsbGJhY2tHdWFyZCB9IGZyb20gJ0BuYXV0aC10b29sa2l0L2NsaWVudC9hbmd1bGFyJztcbiAqXG4gKiBleHBvcnQgY29uc3Qgcm91dGVzOiBSb3V0ZXMgPSBbXG4gKiAgIHsgcGF0aDogJ2F1dGgvY2FsbGJhY2snLCBjYW5BY3RpdmF0ZTogW3NvY2lhbFJlZGlyZWN0Q2FsbGJhY2tHdWFyZF0sIGNvbXBvbmVudDogQ2FsbGJhY2tDb21wb25lbnQgfSxcbiAqIF07XG4gKiBgYGBcbiAqL1xuZXhwb3J0IGNvbnN0IHNvY2lhbFJlZGlyZWN0Q2FsbGJhY2tHdWFyZDogQ2FuQWN0aXZhdGVGbiA9IGFzeW5jICgpOiBQcm9taXNlPGJvb2xlYW4+ID0+IHtcbiAgY29uc3QgYXV0aCA9IGluamVjdChBdXRoU2VydmljZSk7XG4gIGNvbnN0IHBsYXRmb3JtSWQgPSBpbmplY3QoUExBVEZPUk1fSUQpO1xuICBjb25zdCBpc0Jyb3dzZXIgPSBpc1BsYXRmb3JtQnJvd3NlcihwbGF0Zm9ybUlkKTtcblxuICBpZiAoIWlzQnJvd3Nlcikge1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuXG4gIGNvbnN0IHBhcmFtcyA9IG5ldyBVUkxTZWFyY2hQYXJhbXMod2luZG93LmxvY2F0aW9uLnNlYXJjaCk7XG4gIGNvbnN0IGVycm9yID0gcGFyYW1zLmdldCgnZXJyb3InKTtcbiAgY29uc3QgZXhjaGFuZ2VUb2tlbiA9IHBhcmFtcy5nZXQoJ2V4Y2hhbmdlVG9rZW4nKTtcbiAgY29uc3Qgcm91dGVyID0gYXV0aC5nZXRDaGFsbGVuZ2VSb3V0ZXIoKTtcblxuICAvLyBQcm92aWRlciBlcnJvcjogcmVkaXJlY3QgdG8gb2F1dGhFcnJvclxuICBpZiAoZXJyb3IpIHtcbiAgICBhd2FpdCByb3V0ZXIubmF2aWdhdGVUb0Vycm9yKCdvYXV0aCcpO1xuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuXG4gIC8vIE5vIGV4Y2hhbmdlVG9rZW46IGNvb2tpZSBzdWNjZXNzIHBhdGg7IGh5ZHJhdGUgdGhlbiBuYXZpZ2F0ZSB0byBzdWNjZXNzLlxuICAvL1xuICAvLyBOb3RlOiB3ZSBkbyBub3QgXCJhY3RpdmF0ZVwiIHRoZSBjYWxsYmFjayByb3V0ZSB0byBhdm9pZCBjb25zdW1lcnMgbmVlZGluZyB0byByZW5kZXIgYSBwYWdlLlxuICBpZiAoIWV4Y2hhbmdlVG9rZW4pIHtcbiAgICAvLyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09XG4gICAgLy8gQ29va2llcyBtb2RlOiBoeWRyYXRlIHVzZXIgc3RhdGUgYmVmb3JlIHJlZGlyZWN0aW5nXG4gICAgLy8gPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuICAgIC8vIFdIWTogSW4gY29va2llIGRlbGl2ZXJ5LCB0aGUgT0F1dGggY2FsbGJhY2sgY29tcGxldGVzIHZpYSBicm93c2VyIHJlZGlyZWN0cywgc28gdGhlIGZyb250ZW5kXG4gICAgLy8gZG9lcyBub3QgcmVjZWl2ZSBhIEpTT04gQXV0aFJlc3BvbnNlIHRvIHBvcHVsYXRlIHRoZSBTREsncyBjYWNoZWQgYGN1cnJlbnRVc2VyYC5cbiAgICAvL1xuICAgIC8vIFdpdGhvdXQgdGhpcywgc3luYyBndWFyZHMgKGBhdXRoR3VhcmRgKSBjYW4gaW1tZWRpYXRlbHkgcmVkaXJlY3QgdG8gL2xvZ2luIGJlY2F1c2VcbiAgICAvLyBgY3VycmVudFVzZXJgIGlzIHN0aWxsIG51bGwgZXZlbiB0aG91Z2ggY29va2llcyB3ZXJlIHNldCBzdWNjZXNzZnVsbHkuXG4gICAgdHJ5IHtcbiAgICAgIGF3YWl0IGF1dGguZ2V0UHJvZmlsZSgpO1xuICAgICAgYXdhaXQgcm91dGVyLm5hdmlnYXRlVG9TdWNjZXNzKCk7XG4gICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAvLyBPbmx5IHRyZWF0IGF1dGggZmFpbHVyZXMgKDQwMS80MDMpIGFzIE9BdXRoIGVycm9yc1xuICAgICAgLy8gTmV0d29yayBlcnJvcnMgb3Igb3RoZXIgaXNzdWVzIG1pZ2h0IGJlIHRlbXBvcmFyeSAtIHN0aWxsIHRyeSBzdWNjZXNzIHJvdXRlXG4gICAgICBjb25zdCBpc0F1dGhFcnJvciA9XG4gICAgICAgIGVyciBpbnN0YW5jZW9mIE5BdXRoQ2xpZW50RXJyb3IgJiZcbiAgICAgICAgKGVyci5zdGF0dXNDb2RlID09PSA0MDEgfHxcbiAgICAgICAgICBlcnIuc3RhdHVzQ29kZSA9PT0gNDAzIHx8XG4gICAgICAgICAgZXJyLmNvZGUgPT09IE5BdXRoRXJyb3JDb2RlLkFVVEhfVE9LRU5fSU5WQUxJRCB8fFxuICAgICAgICAgIGVyci5jb2RlID09PSBOQXV0aEVycm9yQ29kZS5BVVRIX1NFU1NJT05fRVhQSVJFRCB8fFxuICAgICAgICAgIGVyci5jb2RlID09PSBOQXV0aEVycm9yQ29kZS5BVVRIX1NFU1NJT05fTk9UX0ZPVU5EKTtcblxuICAgICAgaWYgKGlzQXV0aEVycm9yKSB7XG4gICAgICAgIC8vIENvb2tpZXMgd2VyZW4ndCBzZXQgcHJvcGVybHkgLSBPQXV0aCBmYWlsZWRcbiAgICAgICAgYXdhaXQgcm91dGVyLm5hdmlnYXRlVG9FcnJvcignb2F1dGgnKTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIC8vIEZvciBuZXR3b3JrIGVycm9ycyBvciBvdGhlciBpc3N1ZXMsIHByb2NlZWQgdG8gc3VjY2VzcyByb3V0ZVxuICAgICAgICAvLyBUaGUgYXV0aCBndWFyZCB3aWxsIGhhbmRsZSBhdXRoZW50aWNhdGlvbiBzdGF0ZSBvbiB0aGUgbmV4dCByb3V0ZVxuICAgICAgICBhd2FpdCByb3V0ZXIubmF2aWdhdGVUb1N1Y2Nlc3MoKTtcbiAgICAgIH1cbiAgICB9XG4gICAgcmV0dXJuIGZhbHNlO1xuICB9XG5cbiAgLy8gRXhjaGFuZ2UgdG9rZW4gLSBTREsgaGFuZGxlcyBuYXZpZ2F0aW9uIGF1dG9tYXRpY2FsbHlcbiAgYXdhaXQgYXV0aC5leGNoYW5nZVNvY2lhbFJlZGlyZWN0KGV4Y2hhbmdlVG9rZW4pO1xuICByZXR1cm4gZmFsc2U7XG59O1xuIl19
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { NAuthErrorCode, NAuthClientError, NAuthClient } from '@nauth-toolkit/client';
|
|
2
2
|
export * from '@nauth-toolkit/client';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { InjectionToken, Injectable, Inject, inject, PLATFORM_ID } from '@angular/core';
|
|
4
|
+
import { InjectionToken, Injectable, Inject, inject, PLATFORM_ID, Optional } from '@angular/core';
|
|
5
5
|
import { firstValueFrom, BehaviorSubject, Subject, catchError, throwError, from, switchMap, filter as filter$1, take } from 'rxjs';
|
|
6
6
|
import { filter } from 'rxjs/operators';
|
|
7
7
|
import * as i1 from '@angular/common/http';
|
|
8
8
|
import { HttpErrorResponse, HttpClient } from '@angular/common/http';
|
|
9
9
|
import { isPlatformBrowser } from '@angular/common';
|
|
10
10
|
import { Router } from '@angular/router';
|
|
11
|
+
import { __decorate, __param } from 'tslib';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Injection token for providing NAuthClientConfig in Angular apps.
|
|
@@ -39,6 +40,37 @@ class AngularHttpAdapter {
|
|
|
39
40
|
constructor(http) {
|
|
40
41
|
this.http = http;
|
|
41
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Safely parse a JSON response body.
|
|
45
|
+
*
|
|
46
|
+
* Angular's fetch backend (`withFetch()`) will throw a raw `SyntaxError` if
|
|
47
|
+
* `responseType: 'json'` is used and the backend returns HTML (common for
|
|
48
|
+
* proxies, 502 pages, SSR fallbacks, or misrouted requests).
|
|
49
|
+
*
|
|
50
|
+
* To avoid crashing consumer apps, we always request as text and then parse
|
|
51
|
+
* JSON only when the response actually looks like JSON.
|
|
52
|
+
*
|
|
53
|
+
* @param bodyText - Raw response body as text
|
|
54
|
+
* @param contentType - Content-Type header value (if available)
|
|
55
|
+
* @returns Parsed JSON value (unknown)
|
|
56
|
+
* @throws {SyntaxError} When body is non-empty but not valid JSON
|
|
57
|
+
*/
|
|
58
|
+
parseJsonBody(bodyText, contentType) {
|
|
59
|
+
const trimmed = bodyText.trim();
|
|
60
|
+
if (!trimmed)
|
|
61
|
+
return null;
|
|
62
|
+
// If it's clearly HTML, never attempt JSON.parse (some proxies mislabel Content-Type).
|
|
63
|
+
if (trimmed.startsWith('<')) {
|
|
64
|
+
return bodyText;
|
|
65
|
+
}
|
|
66
|
+
const looksLikeJson = trimmed.startsWith('{') || trimmed.startsWith('[');
|
|
67
|
+
const isJsonContentType = typeof contentType === 'string' && contentType.toLowerCase().includes('application/json');
|
|
68
|
+
if (!looksLikeJson && !isJsonContentType) {
|
|
69
|
+
// Return raw text when it doesn't look like JSON (e.g., HTML error pages).
|
|
70
|
+
return bodyText;
|
|
71
|
+
}
|
|
72
|
+
return JSON.parse(trimmed);
|
|
73
|
+
}
|
|
42
74
|
/**
|
|
43
75
|
* Execute HTTP request using Angular's HttpClient.
|
|
44
76
|
*
|
|
@@ -48,29 +80,40 @@ class AngularHttpAdapter {
|
|
|
48
80
|
*/
|
|
49
81
|
async request(config) {
|
|
50
82
|
try {
|
|
51
|
-
// Use Angular's HttpClient - goes through ALL interceptors
|
|
52
|
-
|
|
83
|
+
// Use Angular's HttpClient - goes through ALL interceptors.
|
|
84
|
+
// IMPORTANT: Use responseType 'text' to avoid raw JSON.parse crashes when
|
|
85
|
+
// the backend returns HTML (seen in some proxy/SSR/misroute setups).
|
|
86
|
+
const res = await firstValueFrom(this.http.request(config.method, config.url, {
|
|
53
87
|
body: config.body,
|
|
54
88
|
headers: config.headers,
|
|
55
89
|
withCredentials: config.credentials === 'include',
|
|
56
|
-
observe: '
|
|
90
|
+
observe: 'response',
|
|
91
|
+
responseType: 'text',
|
|
57
92
|
}));
|
|
93
|
+
const contentType = res.headers?.get('content-type');
|
|
94
|
+
const parsed = this.parseJsonBody(res.body ?? '', contentType);
|
|
58
95
|
return {
|
|
59
|
-
data,
|
|
60
|
-
status:
|
|
61
|
-
headers: {}, //
|
|
96
|
+
data: parsed,
|
|
97
|
+
status: res.status,
|
|
98
|
+
headers: {}, // Reserved for future header passthrough if needed
|
|
62
99
|
};
|
|
63
100
|
}
|
|
64
101
|
catch (error) {
|
|
65
102
|
if (error instanceof HttpErrorResponse) {
|
|
66
|
-
// Convert Angular's HttpErrorResponse to NAuthClientError
|
|
67
|
-
|
|
68
|
-
const
|
|
103
|
+
// Convert Angular's HttpErrorResponse to NAuthClientError.
|
|
104
|
+
// When using responseType 'text', `error.error` is typically a string.
|
|
105
|
+
const contentType = error.headers?.get('content-type') ?? null;
|
|
106
|
+
const rawBody = typeof error.error === 'string' ? error.error : '';
|
|
107
|
+
const parsedError = this.parseJsonBody(rawBody, contentType);
|
|
108
|
+
const errorData = typeof parsedError === 'object' && parsedError !== null ? parsedError : {};
|
|
109
|
+
const code = typeof errorData['code'] === 'string' ? errorData['code'] : NAuthErrorCode.INTERNAL_ERROR;
|
|
69
110
|
const message = typeof errorData['message'] === 'string'
|
|
70
|
-
? errorData
|
|
71
|
-
:
|
|
72
|
-
|
|
73
|
-
|
|
111
|
+
? errorData['message']
|
|
112
|
+
: typeof parsedError === 'string' && parsedError.trim()
|
|
113
|
+
? parsedError
|
|
114
|
+
: error.message || `Request failed with status ${error.status}`;
|
|
115
|
+
const timestamp = typeof errorData['timestamp'] === 'string' ? errorData['timestamp'] : undefined;
|
|
116
|
+
const details = typeof errorData['details'] === 'object' ? errorData['details'] : undefined;
|
|
74
117
|
throw new NAuthClientError(code, message, {
|
|
75
118
|
statusCode: error.status,
|
|
76
119
|
timestamp,
|
|
@@ -78,8 +121,12 @@ class AngularHttpAdapter {
|
|
|
78
121
|
isNetworkError: error.status === 0, // Network error (no response from server)
|
|
79
122
|
});
|
|
80
123
|
}
|
|
81
|
-
// Re-throw non-HTTP errors
|
|
82
|
-
|
|
124
|
+
// Re-throw non-HTTP errors as an SDK error so consumers don't see raw parser crashes.
|
|
125
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
126
|
+
throw new NAuthClientError(NAuthErrorCode.INTERNAL_ERROR, message, {
|
|
127
|
+
statusCode: 0,
|
|
128
|
+
isNetworkError: true,
|
|
129
|
+
});
|
|
83
130
|
}
|
|
84
131
|
}
|
|
85
132
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: AngularHttpAdapter, deps: [{ token: i1.HttpClient }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
@@ -219,6 +266,21 @@ class AuthService {
|
|
|
219
266
|
getCurrentChallenge() {
|
|
220
267
|
return this.challengeSubject.value;
|
|
221
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Get challenge router for manual navigation control.
|
|
271
|
+
* Useful for guards that need to handle errors or build custom URLs.
|
|
272
|
+
*
|
|
273
|
+
* @returns ChallengeRouter instance
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* ```typescript
|
|
277
|
+
* const router = this.auth.getChallengeRouter();
|
|
278
|
+
* await router.navigateToError('oauth');
|
|
279
|
+
* ```
|
|
280
|
+
*/
|
|
281
|
+
getChallengeRouter() {
|
|
282
|
+
return this.client.getChallengeRouter();
|
|
283
|
+
}
|
|
222
284
|
// ============================================================================
|
|
223
285
|
// Core Auth Methods
|
|
224
286
|
// ============================================================================
|
|
@@ -1014,20 +1076,24 @@ class AuthInterceptor {
|
|
|
1014
1076
|
* Functional route guard for authentication (Angular 17+).
|
|
1015
1077
|
*
|
|
1016
1078
|
* Protects routes by checking if user is authenticated.
|
|
1017
|
-
* Redirects to login
|
|
1079
|
+
* Redirects to configured session expired route (or login) if not authenticated.
|
|
1018
1080
|
*
|
|
1019
|
-
* @param redirectTo -
|
|
1081
|
+
* @param redirectTo - Optional path to redirect to if not authenticated. If not provided, uses `redirects.sessionExpired` from config (defaults to '/login')
|
|
1020
1082
|
* @returns CanActivateFn guard function
|
|
1021
1083
|
*
|
|
1022
1084
|
* @example
|
|
1023
1085
|
* ```typescript
|
|
1024
|
-
* // In route configuration
|
|
1086
|
+
* // In route configuration - uses config.redirects.sessionExpired
|
|
1025
1087
|
* const routes: Routes = [
|
|
1026
1088
|
* {
|
|
1027
1089
|
* path: 'home',
|
|
1028
1090
|
* component: HomeComponent,
|
|
1029
1091
|
* canActivate: [authGuard()]
|
|
1030
|
-
* }
|
|
1092
|
+
* }
|
|
1093
|
+
* ];
|
|
1094
|
+
*
|
|
1095
|
+
* // Override with custom route
|
|
1096
|
+
* const routes: Routes = [
|
|
1031
1097
|
* {
|
|
1032
1098
|
* path: 'admin',
|
|
1033
1099
|
* component: AdminComponent,
|
|
@@ -1036,14 +1102,17 @@ class AuthInterceptor {
|
|
|
1036
1102
|
* ];
|
|
1037
1103
|
* ```
|
|
1038
1104
|
*/
|
|
1039
|
-
function authGuard(redirectTo
|
|
1105
|
+
function authGuard(redirectTo) {
|
|
1040
1106
|
return () => {
|
|
1041
1107
|
const auth = inject(AuthService);
|
|
1042
1108
|
const router = inject(Router);
|
|
1109
|
+
const config = inject(NAUTH_CLIENT_CONFIG, { optional: true });
|
|
1043
1110
|
if (auth.isAuthenticated()) {
|
|
1044
1111
|
return true;
|
|
1045
1112
|
}
|
|
1046
|
-
|
|
1113
|
+
// Use provided redirectTo, or config.redirects.sessionExpired, or default to '/login'
|
|
1114
|
+
const redirectPath = redirectTo ?? config?.redirects?.sessionExpired ?? '/login';
|
|
1115
|
+
return router.createUrlTree([redirectPath]);
|
|
1047
1116
|
};
|
|
1048
1117
|
}
|
|
1049
1118
|
/**
|
|
@@ -1066,29 +1135,38 @@ function authGuard(redirectTo = '/login') {
|
|
|
1066
1135
|
* })
|
|
1067
1136
|
* ```
|
|
1068
1137
|
*/
|
|
1069
|
-
class AuthGuard {
|
|
1138
|
+
let AuthGuard = class AuthGuard {
|
|
1070
1139
|
auth;
|
|
1071
1140
|
router;
|
|
1141
|
+
config;
|
|
1072
1142
|
/**
|
|
1073
1143
|
* @param auth - Authentication service
|
|
1074
1144
|
* @param router - Angular router
|
|
1145
|
+
* @param config - Optional client configuration (injected automatically)
|
|
1075
1146
|
*/
|
|
1076
|
-
constructor(auth, router) {
|
|
1147
|
+
constructor(auth, router, config) {
|
|
1077
1148
|
this.auth = auth;
|
|
1078
1149
|
this.router = router;
|
|
1150
|
+
this.config = config;
|
|
1079
1151
|
}
|
|
1080
1152
|
/**
|
|
1081
1153
|
* Check if route can be activated.
|
|
1082
1154
|
*
|
|
1083
|
-
* @returns True if authenticated, otherwise redirects to login
|
|
1155
|
+
* @returns True if authenticated, otherwise redirects to configured session expired route (or '/login')
|
|
1084
1156
|
*/
|
|
1085
1157
|
canActivate() {
|
|
1086
1158
|
if (this.auth.isAuthenticated()) {
|
|
1087
1159
|
return true;
|
|
1088
1160
|
}
|
|
1089
|
-
|
|
1161
|
+
// Use config.redirects.sessionExpired or default to '/login'
|
|
1162
|
+
const redirectPath = this.config?.redirects?.sessionExpired ?? '/login';
|
|
1163
|
+
return this.router.createUrlTree([redirectPath]);
|
|
1090
1164
|
}
|
|
1091
|
-
}
|
|
1165
|
+
};
|
|
1166
|
+
AuthGuard = __decorate([
|
|
1167
|
+
__param(2, Optional()),
|
|
1168
|
+
__param(2, Inject(NAUTH_CLIENT_CONFIG))
|
|
1169
|
+
], AuthGuard);
|
|
1092
1170
|
|
|
1093
1171
|
/**
|
|
1094
1172
|
* Social redirect callback route guard.
|
|
@@ -1100,8 +1178,8 @@ class AuthGuard {
|
|
|
1100
1178
|
* - `error` / `error_description` (provider errors)
|
|
1101
1179
|
*
|
|
1102
1180
|
* Behavior:
|
|
1103
|
-
* - If `exchangeToken` exists: exchanges it via backend
|
|
1104
|
-
* - If no `exchangeToken`: treat as cookie-success path
|
|
1181
|
+
* - If `exchangeToken` exists: exchanges it via backend (SDK handles navigation automatically).
|
|
1182
|
+
* - If no `exchangeToken`: treat as cookie-success path (SDK handles navigation automatically).
|
|
1105
1183
|
* - If `error` exists: redirects to oauthError route.
|
|
1106
1184
|
*
|
|
1107
1185
|
* @example
|
|
@@ -1115,7 +1193,6 @@ class AuthGuard {
|
|
|
1115
1193
|
*/
|
|
1116
1194
|
const socialRedirectCallbackGuard = async () => {
|
|
1117
1195
|
const auth = inject(AuthService);
|
|
1118
|
-
const config = inject(NAUTH_CLIENT_CONFIG);
|
|
1119
1196
|
const platformId = inject(PLATFORM_ID);
|
|
1120
1197
|
const isBrowser = isPlatformBrowser(platformId);
|
|
1121
1198
|
if (!isBrowser) {
|
|
@@ -1124,13 +1201,13 @@ const socialRedirectCallbackGuard = async () => {
|
|
|
1124
1201
|
const params = new URLSearchParams(window.location.search);
|
|
1125
1202
|
const error = params.get('error');
|
|
1126
1203
|
const exchangeToken = params.get('exchangeToken');
|
|
1204
|
+
const router = auth.getChallengeRouter();
|
|
1127
1205
|
// Provider error: redirect to oauthError
|
|
1128
1206
|
if (error) {
|
|
1129
|
-
|
|
1130
|
-
window.location.replace(errorUrl);
|
|
1207
|
+
await router.navigateToError('oauth');
|
|
1131
1208
|
return false;
|
|
1132
1209
|
}
|
|
1133
|
-
// No exchangeToken: cookie success path;
|
|
1210
|
+
// No exchangeToken: cookie success path; hydrate then navigate to success.
|
|
1134
1211
|
//
|
|
1135
1212
|
// Note: we do not "activate" the callback route to avoid consumers needing to render a page.
|
|
1136
1213
|
if (!exchangeToken) {
|
|
@@ -1144,26 +1221,31 @@ const socialRedirectCallbackGuard = async () => {
|
|
|
1144
1221
|
// `currentUser` is still null even though cookies were set successfully.
|
|
1145
1222
|
try {
|
|
1146
1223
|
await auth.getProfile();
|
|
1224
|
+
await router.navigateToSuccess();
|
|
1147
1225
|
}
|
|
1148
|
-
catch {
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1226
|
+
catch (err) {
|
|
1227
|
+
// Only treat auth failures (401/403) as OAuth errors
|
|
1228
|
+
// Network errors or other issues might be temporary - still try success route
|
|
1229
|
+
const isAuthError = err instanceof NAuthClientError &&
|
|
1230
|
+
(err.statusCode === 401 ||
|
|
1231
|
+
err.statusCode === 403 ||
|
|
1232
|
+
err.code === NAuthErrorCode.AUTH_TOKEN_INVALID ||
|
|
1233
|
+
err.code === NAuthErrorCode.AUTH_SESSION_EXPIRED ||
|
|
1234
|
+
err.code === NAuthErrorCode.AUTH_SESSION_NOT_FOUND);
|
|
1235
|
+
if (isAuthError) {
|
|
1236
|
+
// Cookies weren't set properly - OAuth failed
|
|
1237
|
+
await router.navigateToError('oauth');
|
|
1238
|
+
}
|
|
1239
|
+
else {
|
|
1240
|
+
// For network errors or other issues, proceed to success route
|
|
1241
|
+
// The auth guard will handle authentication state on the next route
|
|
1242
|
+
await router.navigateToSuccess();
|
|
1243
|
+
}
|
|
1152
1244
|
}
|
|
1153
|
-
const successUrl = config.redirects?.success || '/';
|
|
1154
|
-
window.location.replace(successUrl);
|
|
1155
|
-
return false;
|
|
1156
|
-
}
|
|
1157
|
-
// Exchange token and route accordingly
|
|
1158
|
-
const response = await auth.exchangeSocialRedirect(exchangeToken);
|
|
1159
|
-
if (response.challengeName) {
|
|
1160
|
-
const challengeBase = config.redirects?.challengeBase || '/auth/challenge';
|
|
1161
|
-
const challengeRoute = response.challengeName.toLowerCase().replace(/_/g, '-');
|
|
1162
|
-
window.location.replace(`${challengeBase}/${challengeRoute}`);
|
|
1163
1245
|
return false;
|
|
1164
1246
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1247
|
+
// Exchange token - SDK handles navigation automatically
|
|
1248
|
+
await auth.exchangeSocialRedirect(exchangeToken);
|
|
1167
1249
|
return false;
|
|
1168
1250
|
};
|
|
1169
1251
|
|