mates-auth 1.0.0-beta.1
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/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/sso.d.ts +90 -0
- package/dist/sso.d.ts.map +1 -0
- package/dist/sso.js +303 -0
- package/dist/sso.js.map +1 -0
- package/dist/use-arctic.d.ts +101 -0
- package/dist/use-arctic.d.ts.map +1 -0
- package/dist/use-arctic.js +538 -0
- package/dist/use-arctic.js.map +1 -0
- package/dist/use-jwt.d.ts +70 -0
- package/dist/use-jwt.d.ts.map +1 -0
- package/dist/use-jwt.js +274 -0
- package/dist/use-jwt.js.map +1 -0
- package/package.json +31 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { useJWT, auth, type AuthDuration, type AuthClaims, type AuthContext, type JWTOptions, type AuthLoginOptions, } from "./use-jwt.js";
|
|
2
|
+
export { useArctic, arcticProvider, listArcticProviders, type ArcticBuiltInProvider, type ArcticTokenSet, type ArcticProfile, type ArcticProviderConfig, type ArcticProvidersConfig, type ArcticSuccessHandler, type ArcticUseOptions, type ArcticProviderHandler, type ArcticCustomProvider, type ArcticProfileLoader, } from "./use-arctic.js";
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,IAAI,EACJ,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,SAAS,EACT,cAAc,EACd,mBAAmB,EACnB,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,IAAI,GAML,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,SAAS,EACT,cAAc,EACd,mBAAmB,GAWpB,MAAM,iBAAiB,CAAC"}
|
package/dist/sso.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mates-fullstack-auth — sso.ts
|
|
3
|
+
*
|
|
4
|
+
* Optional cross-domain SSO middleware.
|
|
5
|
+
*
|
|
6
|
+
* Setup:
|
|
7
|
+
* Auth server (auth.com): useSsoProvider({ secret, login: "/login" })
|
|
8
|
+
* App server (app1.com): useSsoClient({ authUrl, secret, protected: ["/dashboard"] })
|
|
9
|
+
*
|
|
10
|
+
* Flow:
|
|
11
|
+
* app1.com/dashboard → auth.com/api/sso/code?redirect=... → login (if needed)
|
|
12
|
+
* → code JWT → app1.com/auth/sso/callback → auth.login()
|
|
13
|
+
*/
|
|
14
|
+
export interface SsoProviderOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Shared secret between auth server and app servers.
|
|
17
|
+
* Used to sign and verify the short-lived code JWTs.
|
|
18
|
+
*/
|
|
19
|
+
secret: string;
|
|
20
|
+
/**
|
|
21
|
+
* Path to the login page on the auth server.
|
|
22
|
+
* Unauthenticated users are redirected here with a `?next` param
|
|
23
|
+
* so they return to `/api/sso/code` after login.
|
|
24
|
+
* @default "/login"
|
|
25
|
+
*/
|
|
26
|
+
login?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Lifetime of the code JWT in seconds.
|
|
29
|
+
* @default 30
|
|
30
|
+
*/
|
|
31
|
+
codeLifetime?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Allowed redirect origins for the `redirect` parameter.
|
|
34
|
+
* Only URLs starting with these prefixes are accepted.
|
|
35
|
+
* Must include the protocol (e.g. "https://app1.com").
|
|
36
|
+
* When unset, only relative paths (starting with `/`) are allowed.
|
|
37
|
+
*/
|
|
38
|
+
allowedOrigins?: string[];
|
|
39
|
+
}
|
|
40
|
+
export interface SsoClientOptions {
|
|
41
|
+
/**
|
|
42
|
+
* Base URL of the auth server (e.g. "https://auth.com").
|
|
43
|
+
*/
|
|
44
|
+
authUrl: string;
|
|
45
|
+
/**
|
|
46
|
+
* Shared secret — must match exactly what the auth server uses.
|
|
47
|
+
*/
|
|
48
|
+
secret: string;
|
|
49
|
+
/**
|
|
50
|
+
* Paths that require authentication.
|
|
51
|
+
* Unauthenticated visitors are redirected to the auth server.
|
|
52
|
+
* @default []
|
|
53
|
+
*/
|
|
54
|
+
protected?: string[];
|
|
55
|
+
/**
|
|
56
|
+
* Where to redirect after a successful SSO login.
|
|
57
|
+
* @default "/"
|
|
58
|
+
*/
|
|
59
|
+
afterLogin?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Register SSO provider endpoints on the auth server.
|
|
63
|
+
*
|
|
64
|
+
* Creates:
|
|
65
|
+
* GET /api/sso/code — generates a code JWT if authenticated
|
|
66
|
+
* GET /api/sso/after-login — where the login page redirects after auth
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* useSsoProvider({
|
|
70
|
+
* secret: process.env.SSO_SECRET!,
|
|
71
|
+
* login: "/login",
|
|
72
|
+
* });
|
|
73
|
+
*/
|
|
74
|
+
export declare function useSsoProvider(options: SsoProviderOptions): void;
|
|
75
|
+
/**
|
|
76
|
+
* Register SSO client endpoints and guarded routes on an app server.
|
|
77
|
+
*
|
|
78
|
+
* Creates:
|
|
79
|
+
* GET /auth/sso/callback — verifies the code JWT, calls auth.login()
|
|
80
|
+
* onRequest guard — redirects protected routes to the auth server
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* useSsoClient({
|
|
84
|
+
* authUrl: "https://auth.com",
|
|
85
|
+
* secret: process.env.SSO_SECRET!,
|
|
86
|
+
* protected: ["/dashboard", "/settings"],
|
|
87
|
+
* });
|
|
88
|
+
*/
|
|
89
|
+
export declare function useSsoClient(options: SsoClientOptions): void;
|
|
90
|
+
//# sourceMappingURL=sso.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sso.d.ts","sourceRoot":"","sources":["../src/sso.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAQH,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA4CD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CA0HhE;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAoH5D"}
|
package/dist/sso.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mates-fullstack-auth — sso.ts
|
|
3
|
+
*
|
|
4
|
+
* Optional cross-domain SSO middleware.
|
|
5
|
+
*
|
|
6
|
+
* Setup:
|
|
7
|
+
* Auth server (auth.com): useSsoProvider({ secret, login: "/login" })
|
|
8
|
+
* App server (app1.com): useSsoClient({ authUrl, secret, protected: ["/dashboard"] })
|
|
9
|
+
*
|
|
10
|
+
* Flow:
|
|
11
|
+
* app1.com/dashboard → auth.com/api/sso/code?redirect=... → login (if needed)
|
|
12
|
+
* → code JWT → app1.com/auth/sso/callback → auth.login()
|
|
13
|
+
*/
|
|
14
|
+
import crypto from "node:crypto";
|
|
15
|
+
import { onRequest, jwt } from "mates-fullstack";
|
|
16
|
+
import { auth } from "./use-jwt.js";
|
|
17
|
+
// ─── Cookie helpers (internal, temporary) ─────────────────────────────────────
|
|
18
|
+
const SSO_REDIRECT_COOKIE = "sso_redirect";
|
|
19
|
+
const SSO_AFTER_LOGIN_COOKIE = "sso_after_login";
|
|
20
|
+
const SSO_CODE_PARAM = "code";
|
|
21
|
+
function _setTempCookie(ctx, name, value, path) {
|
|
22
|
+
ctx.cookie.set(name, value, {
|
|
23
|
+
httpOnly: true,
|
|
24
|
+
sameSite: "lax",
|
|
25
|
+
path,
|
|
26
|
+
maxAge: 300, // 5 min
|
|
27
|
+
secure: process.env.NODE_ENV === "production",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function _clearTempCookie(ctx, name, path) {
|
|
31
|
+
ctx.cookie.delete(name, { path });
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the public origin for the current request.
|
|
35
|
+
* Used by the client to build the callback URL for the auth server.
|
|
36
|
+
*/
|
|
37
|
+
function _publicOrigin(req) {
|
|
38
|
+
const url = new URL(req.url);
|
|
39
|
+
const host = req.headers.get("x-forwarded-host") ?? url.host;
|
|
40
|
+
const proto = req.headers.get("x-forwarded-proto") ?? url.protocol.slice(0, -1);
|
|
41
|
+
return `${proto}://${host}`;
|
|
42
|
+
}
|
|
43
|
+
// ─── Provider side (auth.com) ────────────────────────────────────────────────
|
|
44
|
+
const SSO_CODE_PATH = "/api/sso/code";
|
|
45
|
+
const SSO_AFTER_LOGIN_PATH = "/api/sso/after-login";
|
|
46
|
+
/**
|
|
47
|
+
* Register SSO provider endpoints on the auth server.
|
|
48
|
+
*
|
|
49
|
+
* Creates:
|
|
50
|
+
* GET /api/sso/code — generates a code JWT if authenticated
|
|
51
|
+
* GET /api/sso/after-login — where the login page redirects after auth
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* useSsoProvider({
|
|
55
|
+
* secret: process.env.SSO_SECRET!,
|
|
56
|
+
* login: "/login",
|
|
57
|
+
* });
|
|
58
|
+
*/
|
|
59
|
+
export function useSsoProvider(options) {
|
|
60
|
+
const secret = options.secret;
|
|
61
|
+
if (!secret || secret.length < 8) {
|
|
62
|
+
throw new Error("useSsoProvider: secret must be at least 8 characters.");
|
|
63
|
+
}
|
|
64
|
+
const login = options.login ?? "/login";
|
|
65
|
+
const codeLifetime = options.codeLifetime ?? 30;
|
|
66
|
+
const tokens = jwt(secret);
|
|
67
|
+
onRequest(async (c) => {
|
|
68
|
+
const url = new URL(c.req.raw.url);
|
|
69
|
+
const pathname = url.pathname;
|
|
70
|
+
// ── Entry: GET /api/sso/code?redirect=... ──────────────────────────────
|
|
71
|
+
if (pathname === SSO_CODE_PATH) {
|
|
72
|
+
if (c.req.method !== "GET") {
|
|
73
|
+
return new Response(JSON.stringify({ error: "Method not allowed" }), {
|
|
74
|
+
status: 405,
|
|
75
|
+
headers: { "content-type": "application/json" },
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
const redirect = url.searchParams.get("redirect");
|
|
79
|
+
if (!redirect) {
|
|
80
|
+
return new Response(JSON.stringify({ error: 'Missing "redirect" parameter' }), { status: 400, headers: { "content-type": "application/json" } });
|
|
81
|
+
}
|
|
82
|
+
if (!_isValidRedirect(redirect, options.allowedOrigins)) {
|
|
83
|
+
return new Response(JSON.stringify({ error: "Invalid redirect target" }), { status: 400, headers: { "content-type": "application/json" } });
|
|
84
|
+
}
|
|
85
|
+
// Store the redirect target in a cookie so it survives login redirect
|
|
86
|
+
_setTempCookie(c, SSO_REDIRECT_COOKIE, redirect, SSO_CODE_PATH);
|
|
87
|
+
_setTempCookie(c, SSO_AFTER_LOGIN_COOKIE, redirect, SSO_AFTER_LOGIN_PATH);
|
|
88
|
+
const userId = (c.auth?.sub ?? c.auth?.userId);
|
|
89
|
+
if (!userId) {
|
|
90
|
+
// Not authenticated — redirect to login page
|
|
91
|
+
const next = encodeURIComponent(SSO_CODE_PATH);
|
|
92
|
+
const loginUrl = `${login}?next=${next}`;
|
|
93
|
+
return new Response(null, {
|
|
94
|
+
status: 302,
|
|
95
|
+
headers: { location: loginUrl },
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// Authenticated — generate a short-lived code JWT and redirect
|
|
99
|
+
let aud;
|
|
100
|
+
try {
|
|
101
|
+
aud = new URL(redirect).origin;
|
|
102
|
+
}
|
|
103
|
+
catch { }
|
|
104
|
+
const code = await tokens.sign({
|
|
105
|
+
sub: userId,
|
|
106
|
+
..._pickClaims(c.auth ?? {}),
|
|
107
|
+
jti: crypto.randomUUID(),
|
|
108
|
+
...(aud ? { aud } : {}),
|
|
109
|
+
}, { expiresIn: codeLifetime });
|
|
110
|
+
_clearTempCookie(c, SSO_REDIRECT_COOKIE, SSO_CODE_PATH);
|
|
111
|
+
_clearTempCookie(c, SSO_AFTER_LOGIN_COOKIE, SSO_AFTER_LOGIN_PATH);
|
|
112
|
+
const stateParam = url.searchParams.get("state") || "";
|
|
113
|
+
const separator = redirect.includes("?") ? "&" : "?";
|
|
114
|
+
const location = `${redirect}${separator}${SSO_CODE_PARAM}=${code}${stateParam ? `&state=${encodeURIComponent(stateParam)}` : ""}`;
|
|
115
|
+
return new Response(null, { status: 302, headers: { location } });
|
|
116
|
+
}
|
|
117
|
+
// ── After login: GET /api/sso/after-login ──────────────────────────────
|
|
118
|
+
if (pathname === SSO_AFTER_LOGIN_PATH) {
|
|
119
|
+
if (c.req.method !== "GET") {
|
|
120
|
+
return new Response(JSON.stringify({ error: "Method not allowed" }), {
|
|
121
|
+
status: 405,
|
|
122
|
+
headers: { "content-type": "application/json" },
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const redirect = c.cookie.get(SSO_AFTER_LOGIN_COOKIE) ??
|
|
126
|
+
c.cookie.get(SSO_REDIRECT_COOKIE);
|
|
127
|
+
_clearTempCookie(c, SSO_AFTER_LOGIN_COOKIE, SSO_AFTER_LOGIN_PATH);
|
|
128
|
+
const userId = (c.auth?.sub ?? c.auth?.userId);
|
|
129
|
+
if (!userId) {
|
|
130
|
+
const loginUrl = `${login}?next=${encodeURIComponent(SSO_AFTER_LOGIN_PATH)}`;
|
|
131
|
+
return new Response(null, {
|
|
132
|
+
status: 302,
|
|
133
|
+
headers: { location: loginUrl },
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (redirect) {
|
|
137
|
+
let aud;
|
|
138
|
+
try {
|
|
139
|
+
aud = new URL(redirect).origin;
|
|
140
|
+
}
|
|
141
|
+
catch { }
|
|
142
|
+
const code = await tokens.sign({
|
|
143
|
+
sub: userId,
|
|
144
|
+
..._pickClaims(c.auth ?? {}),
|
|
145
|
+
jti: crypto.randomUUID(),
|
|
146
|
+
...(aud ? { aud } : {}),
|
|
147
|
+
}, { expiresIn: codeLifetime });
|
|
148
|
+
const location = `${redirect}${redirect.includes("?") ? "&" : "?"}${SSO_CODE_PARAM}=${code}`;
|
|
149
|
+
return new Response(null, { status: 302, headers: { location } });
|
|
150
|
+
}
|
|
151
|
+
// No redirect stored — go to home
|
|
152
|
+
return new Response(null, { status: 302, headers: { location: "/" } });
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
// ─── Client side (app1.com, app2.com) ─────────────────────────────────────────
|
|
157
|
+
const SSO_CALLBACK_PATH = "/auth/sso/callback";
|
|
158
|
+
/**
|
|
159
|
+
* Register SSO client endpoints and guarded routes on an app server.
|
|
160
|
+
*
|
|
161
|
+
* Creates:
|
|
162
|
+
* GET /auth/sso/callback — verifies the code JWT, calls auth.login()
|
|
163
|
+
* onRequest guard — redirects protected routes to the auth server
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* useSsoClient({
|
|
167
|
+
* authUrl: "https://auth.com",
|
|
168
|
+
* secret: process.env.SSO_SECRET!,
|
|
169
|
+
* protected: ["/dashboard", "/settings"],
|
|
170
|
+
* });
|
|
171
|
+
*/
|
|
172
|
+
export function useSsoClient(options) {
|
|
173
|
+
const { authUrl, secret, afterLogin } = options;
|
|
174
|
+
const protectedPaths = options.protected ?? [];
|
|
175
|
+
if (!secret || secret.length < 8) {
|
|
176
|
+
throw new Error("useSsoClient: secret must be at least 8 characters.");
|
|
177
|
+
}
|
|
178
|
+
if (!authUrl) {
|
|
179
|
+
throw new Error("useSsoClient: authUrl is required.");
|
|
180
|
+
}
|
|
181
|
+
const tokens = jwt(secret);
|
|
182
|
+
const _consumedJtis = new Set();
|
|
183
|
+
// ── Callback: GET /auth/sso/callback?code=... ────────────────────────────
|
|
184
|
+
onRequest(async (c) => {
|
|
185
|
+
const url = new URL(c.req.raw.url);
|
|
186
|
+
if (url.pathname !== SSO_CALLBACK_PATH)
|
|
187
|
+
return;
|
|
188
|
+
const code = url.searchParams.get(SSO_CODE_PARAM);
|
|
189
|
+
const next = url.searchParams.get("next");
|
|
190
|
+
if (!code) {
|
|
191
|
+
return new Response(JSON.stringify({ error: `Missing "${SSO_CODE_PARAM}" parameter` }), { status: 400, headers: { "content-type": "application/json" } });
|
|
192
|
+
}
|
|
193
|
+
// Verify the code JWT — ensures it was signed by the auth server
|
|
194
|
+
const payload = await tokens.verify(code);
|
|
195
|
+
if (!payload) {
|
|
196
|
+
return new Response(JSON.stringify({ error: "Invalid or expired code" }), { status: 401, headers: { "content-type": "application/json" } });
|
|
197
|
+
}
|
|
198
|
+
// Replay protection — reject reused JTIs
|
|
199
|
+
if (payload.jti && typeof payload.jti === "string") {
|
|
200
|
+
if (_consumedJtis.has(payload.jti)) {
|
|
201
|
+
return new Response(JSON.stringify({ error: "Code already used" }), {
|
|
202
|
+
status: 401,
|
|
203
|
+
headers: { "content-type": "application/json" },
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
_consumedJtis.add(payload.jti);
|
|
207
|
+
if (_consumedJtis.size > 10_000)
|
|
208
|
+
_consumedJtis.clear();
|
|
209
|
+
}
|
|
210
|
+
// Verify audience matches this app
|
|
211
|
+
const appOrigin = _publicOrigin(c.req.raw);
|
|
212
|
+
if (payload.aud && payload.aud !== appOrigin) {
|
|
213
|
+
return new Response(JSON.stringify({ error: "Code audience mismatch" }), {
|
|
214
|
+
status: 401,
|
|
215
|
+
headers: { "content-type": "application/json" },
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
// Verify state parameter
|
|
219
|
+
const stateParam = url.searchParams.get("state");
|
|
220
|
+
const storedState = c.cookie.get("sso_state");
|
|
221
|
+
if (stateParam && storedState && stateParam !== storedState) {
|
|
222
|
+
return new Response(JSON.stringify({ error: "Invalid state" }), {
|
|
223
|
+
status: 401,
|
|
224
|
+
headers: { "content-type": "application/json" },
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
// Clear the state cookie
|
|
228
|
+
c.cookie.delete("sso_state", { path: "/" });
|
|
229
|
+
// Extract claims — strip JWT timing fields
|
|
230
|
+
const { iat: _iat, exp: _exp, nbf: _nbf, jti: _jti, ...claims } = payload;
|
|
231
|
+
await auth.login(c, claims);
|
|
232
|
+
const location = next ?? afterLogin ?? "/";
|
|
233
|
+
return new Response(null, { status: 302, headers: { location } });
|
|
234
|
+
});
|
|
235
|
+
// ── Guard protected routes ──────────────────────────────────────────────
|
|
236
|
+
if (protectedPaths.length > 0) {
|
|
237
|
+
onRequest((c) => {
|
|
238
|
+
const url = new URL(c.req.raw.url);
|
|
239
|
+
const pathname = url.pathname;
|
|
240
|
+
const isProtected = protectedPaths.some((p) => pathname === p || pathname.startsWith(p + "/"));
|
|
241
|
+
if (!isProtected)
|
|
242
|
+
return;
|
|
243
|
+
// Already authenticated
|
|
244
|
+
const userId = c.auth?.sub ?? c.auth?.userId;
|
|
245
|
+
if (userId)
|
|
246
|
+
return;
|
|
247
|
+
// Redirect to auth server
|
|
248
|
+
const origin = _publicOrigin(c.req.raw);
|
|
249
|
+
const callbackUrl = `${origin}${SSO_CALLBACK_PATH}`;
|
|
250
|
+
const codeUrl = `${authUrl}${SSO_CODE_PATH}`;
|
|
251
|
+
const state = crypto.randomUUID();
|
|
252
|
+
c.cookie.set("sso_state", state, {
|
|
253
|
+
httpOnly: true,
|
|
254
|
+
sameSite: "lax",
|
|
255
|
+
path: "/",
|
|
256
|
+
maxAge: 300,
|
|
257
|
+
secure: process.env.NODE_ENV === "production",
|
|
258
|
+
});
|
|
259
|
+
const redirectTarget = `${codeUrl}?redirect=${encodeURIComponent(callbackUrl + "?next=" + encodeURIComponent(pathname))}&state=${encodeURIComponent(state)}`;
|
|
260
|
+
return new Response(null, {
|
|
261
|
+
status: 302,
|
|
262
|
+
headers: { location: redirectTarget },
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
268
|
+
/**
|
|
269
|
+
* Validate the redirect URL to prevent open redirect attacks.
|
|
270
|
+
* - Relative paths (starting with `/`) are always allowed.
|
|
271
|
+
* - Absolute URLs with a host are only allowed if their origin matches
|
|
272
|
+
* one of the allowedOrigins.
|
|
273
|
+
*/
|
|
274
|
+
function _isValidRedirect(redirect, allowedOrigins) {
|
|
275
|
+
// Relative paths are safe — always allow
|
|
276
|
+
if (redirect.startsWith("/"))
|
|
277
|
+
return true;
|
|
278
|
+
// Absolute URL — must match an allowed origin
|
|
279
|
+
if (!allowedOrigins || allowedOrigins.length === 0)
|
|
280
|
+
return false;
|
|
281
|
+
try {
|
|
282
|
+
const u = new URL(redirect);
|
|
283
|
+
const origin = `${u.protocol}//${u.host}`;
|
|
284
|
+
return allowedOrigins.some((allowed) => allowed === origin || redirect.startsWith(allowed));
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Pick safe display claims from ctx.auth to include in the code JWT.
|
|
292
|
+
*/
|
|
293
|
+
function _pickClaims(auth) {
|
|
294
|
+
const safe = {};
|
|
295
|
+
const keys = ["userId", "userName", "username", "name", "email", "roles"];
|
|
296
|
+
for (const key of keys) {
|
|
297
|
+
const val = auth[key];
|
|
298
|
+
if (val !== undefined)
|
|
299
|
+
safe[key] = val;
|
|
300
|
+
}
|
|
301
|
+
return safe;
|
|
302
|
+
}
|
|
303
|
+
//# sourceMappingURL=sso.js.map
|
package/dist/sso.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sso.js","sourceRoot":"","sources":["../src/sso.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { type OAuth2Tokens } from "arctic";
|
|
2
|
+
import { type Context, type CookieOptions } from "mates-fullstack";
|
|
3
|
+
export type ArcticBuiltInProvider = "apple" | "discord" | "facebook" | "github" | "gitlab" | "google" | "linkedin" | "microsoft" | "spotify" | "twitter";
|
|
4
|
+
export interface ArcticTokenSet {
|
|
5
|
+
accessToken: string | null;
|
|
6
|
+
refreshToken: string | null;
|
|
7
|
+
idToken: string | null;
|
|
8
|
+
tokenType: string | null;
|
|
9
|
+
expiresAt: Date | null;
|
|
10
|
+
scopes: string[] | null;
|
|
11
|
+
raw: object;
|
|
12
|
+
}
|
|
13
|
+
export interface ArcticProfile<Raw = unknown> {
|
|
14
|
+
provider: string;
|
|
15
|
+
id: string;
|
|
16
|
+
email: string | null;
|
|
17
|
+
name: string | null;
|
|
18
|
+
avatar: string | null;
|
|
19
|
+
username?: string | null;
|
|
20
|
+
raw: Raw;
|
|
21
|
+
tokens: ArcticTokenSet;
|
|
22
|
+
}
|
|
23
|
+
export interface ArcticProviderConfig {
|
|
24
|
+
clientId: string;
|
|
25
|
+
clientSecret?: string | null;
|
|
26
|
+
redirectUri?: string;
|
|
27
|
+
scopes?: string[];
|
|
28
|
+
/** Microsoft Entra ID tenant. Defaults to "common". */
|
|
29
|
+
tenant?: string;
|
|
30
|
+
/** Apple Team ID. */
|
|
31
|
+
teamId?: string;
|
|
32
|
+
/** Apple Key ID. */
|
|
33
|
+
keyId?: string;
|
|
34
|
+
/** Apple private key in PKCS#8 PEM format. */
|
|
35
|
+
privateKey?: string;
|
|
36
|
+
/** GitLab base URL. Defaults to https://gitlab.com. */
|
|
37
|
+
baseURL?: string;
|
|
38
|
+
/** Override the built-in profile fetcher or add one for custom providers. */
|
|
39
|
+
profile?: ArcticProfileLoader;
|
|
40
|
+
/** Override whether this provider uses PKCE. */
|
|
41
|
+
usesPKCE?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Called after the provider returns a profile.
|
|
44
|
+
* Return the claims to bake into the JWT — typically after an upsert in your DB.
|
|
45
|
+
*/
|
|
46
|
+
onSuccess: ArcticSuccessHandler;
|
|
47
|
+
}
|
|
48
|
+
export type ArcticProvidersConfig = Partial<Record<ArcticBuiltInProvider | string, ArcticProviderConfig | ArcticCustomProvider>>;
|
|
49
|
+
/**
|
|
50
|
+
* Called after the provider returns a verified profile.
|
|
51
|
+
* You are fully in control — call `auth.login(ctx, claims)` to issue tokens,
|
|
52
|
+
* update the DB to link an account, or do nothing at all.
|
|
53
|
+
* Return a path string to override the post-OAuth redirect.
|
|
54
|
+
*/
|
|
55
|
+
export type ArcticSuccessHandler = (profile: ArcticProfile, ctx: Context) => string | void | Promise<string | void>;
|
|
56
|
+
export interface ArcticUseOptions {
|
|
57
|
+
/** Route base for OAuth endpoints. Default: "/auth". */
|
|
58
|
+
basePath?: string;
|
|
59
|
+
/** Default redirect after login. Default: "/". */
|
|
60
|
+
afterLogin?: string;
|
|
61
|
+
/** Redirect after OAuth failure. If omitted, failures return JSON. */
|
|
62
|
+
onErrorRedirect?: string;
|
|
63
|
+
/** Cookie path/domain/sameSite/secure controls for temporary OAuth cookies. */
|
|
64
|
+
cookie?: Pick<CookieOptions, "path" | "domain" | "sameSite" | "secure">;
|
|
65
|
+
/** Temporary OAuth state cookie lifetime in seconds. Default: 10 minutes. */
|
|
66
|
+
stateMaxAge?: number;
|
|
67
|
+
}
|
|
68
|
+
export interface ArcticProviderHandler {
|
|
69
|
+
defaultScopes: string[];
|
|
70
|
+
usesPKCE: boolean;
|
|
71
|
+
start(config: ArcticProviderConfig, redirectUri: string, state: string, codeVerifier: string | null): Promise<URL> | URL;
|
|
72
|
+
callback(config: ArcticProviderConfig, redirectUri: string, code: string, codeVerifier: string | null, providerName: string): Promise<ArcticProfile>;
|
|
73
|
+
}
|
|
74
|
+
export interface ArcticCustomProvider extends ArcticProviderConfig {
|
|
75
|
+
handler: ArcticProviderHandler;
|
|
76
|
+
}
|
|
77
|
+
export type ArcticProfileLoader = (tokens: OAuth2Tokens, providerName: string) => Promise<Omit<ArcticProfile, "provider" | "tokens">>;
|
|
78
|
+
/**
|
|
79
|
+
* Register social login routes for every configured provider.
|
|
80
|
+
*
|
|
81
|
+
* The framework automatically handles:
|
|
82
|
+
* GET /auth/:provider — redirect to provider
|
|
83
|
+
* GET /auth/:provider/callback — exchange code, call onSuccess, set cookies
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* useArctic({
|
|
87
|
+
* google: {
|
|
88
|
+
* clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
89
|
+
* clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
90
|
+
* onSuccess: async (profile) => {
|
|
91
|
+
* const user = await db.upsert({ provider: profile.provider, providerId: profile.id, ... });
|
|
92
|
+
* return { userId: user.id, email: user.email, roles: user.roles };
|
|
93
|
+
* },
|
|
94
|
+
* },
|
|
95
|
+
* });
|
|
96
|
+
*/
|
|
97
|
+
export declare function useArctic(providers: ArcticProvidersConfig, options?: ArcticUseOptions): void;
|
|
98
|
+
/** Define a custom Arctic provider adapter for providers without a built-in profile normalizer. */
|
|
99
|
+
export declare function arcticProvider(config: ArcticCustomProvider): ArcticCustomProvider;
|
|
100
|
+
export declare function listArcticProviders(): ArcticBuiltInProvider[];
|
|
101
|
+
//# sourceMappingURL=use-arctic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-arctic.d.ts","sourceRoot":"","sources":["../src/use-arctic.ts"],"names":[],"mappings":"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"}
|