@nauticana/sail 1.1.2 → 1.1.3
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/package.json +1 -1
- package/src/config.ts +9 -0
- package/src/service/auth.service.ts +30 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nauticana/sail",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "Metadata-driven Angular component library for CRUD admin frontends backed by the keel Go backend. Tables, forms, navigation, OTP/2FA/social auth, billing, payout onboarding.",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"scripts": {
|
package/src/config.ts
CHANGED
|
@@ -59,6 +59,15 @@ export interface SailGuiConfig {
|
|
|
59
59
|
defaultPolicyLanguage?: string;
|
|
60
60
|
/** Route to navigate to after account deletion. Defaults to '/login/local'. */
|
|
61
61
|
accountDeletedRoute?: string;
|
|
62
|
+
|
|
63
|
+
// ── OAuth authorization-flow bridge (for an OAuth AS on a different host) ──
|
|
64
|
+
/** Backend path that mirrors the bearer JWT into an HttpOnly session cookie so
|
|
65
|
+
* a cross-site OAuth `/authorize` can see the session, e.g. '/api/v1/auth/session-cookie'.
|
|
66
|
+
* When set, login POSTs here before handing off to an allowed ?return= host. */
|
|
67
|
+
sessionCookieUrl?: string;
|
|
68
|
+
/** Hostnames a post-login `?return=` may redirect to (open-redirect allowlist),
|
|
69
|
+
* e.g. ['mcp.daxoom.com']. Anything not listed is ignored. */
|
|
70
|
+
allowedReturnHosts?: string[];
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
export const SAIL_GUI_CONFIG = new InjectionToken<SailGuiConfig>('SailGuiConfig');
|
|
@@ -110,10 +110,40 @@ export abstract class BaseAuthService extends BaseRestService {
|
|
|
110
110
|
this.token = token;
|
|
111
111
|
this.isLoggedIn.set(true);
|
|
112
112
|
localStorage.setItem('jwt', token);
|
|
113
|
+
const ret = this.validatedReturnUrl();
|
|
114
|
+
if (ret && this.guiConfig.sessionCookieUrl) {
|
|
115
|
+
// OAuth flow: mirror the bearer into a cookie, then hand off to the
|
|
116
|
+
// (cross-origin) authorization server via a full navigation.
|
|
117
|
+
this.http.post(this.url(this.guiConfig.sessionCookieUrl), {}, { withCredentials: true })
|
|
118
|
+
.pipe(take(1))
|
|
119
|
+
.subscribe({
|
|
120
|
+
next: () => window.location.assign(ret),
|
|
121
|
+
error: () => window.location.assign(ret),
|
|
122
|
+
});
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
113
125
|
this.loadAppData();
|
|
114
126
|
this.initRoutes();
|
|
115
127
|
}
|
|
116
128
|
|
|
129
|
+
/** A post-login `?return=` URL, only when its host is in allowedReturnHosts and
|
|
130
|
+
* the scheme is https (or loopback) — an open-redirect guard. '' otherwise. */
|
|
131
|
+
private validatedReturnUrl(): string {
|
|
132
|
+
const raw = new URLSearchParams(window.location.search).get('return');
|
|
133
|
+
if (!raw) return '';
|
|
134
|
+
try {
|
|
135
|
+
const u = new URL(raw, window.location.origin);
|
|
136
|
+
if (u.username || u.password) return ''; // reject userinfo ("user@host") spoofing
|
|
137
|
+
const host = u.hostname.toLowerCase();
|
|
138
|
+
const okScheme = u.protocol === 'https:' || host === 'localhost' || host === '127.0.0.1';
|
|
139
|
+
const allowed = (this.guiConfig.allowedReturnHosts ?? []).some((h) => h.toLowerCase() === host);
|
|
140
|
+
if (okScheme && allowed) {
|
|
141
|
+
return u.toString();
|
|
142
|
+
}
|
|
143
|
+
} catch { /* malformed → reject */ }
|
|
144
|
+
return '';
|
|
145
|
+
}
|
|
146
|
+
|
|
117
147
|
login(username: string, password: string) {
|
|
118
148
|
// withCredentials so the browser sends the server-set `keel_td` cookie
|
|
119
149
|
// (keel v0.9 trusted-device secret). When it maps to a trusted row, keel
|