@spfn/auth 0.1.0-alpha.88 → 0.2.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/README.md +1385 -1199
- package/dist/config.d.ts +405 -0
- package/dist/config.js +240 -0
- package/dist/config.js.map +1 -0
- package/dist/dto-81uR9gzF.d.ts +630 -0
- package/dist/errors.d.ts +196 -0
- package/dist/errors.js +173 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +273 -14
- package/dist/index.js +511 -6665
- package/dist/index.js.map +1 -1
- package/dist/nextjs/api.js +345 -0
- package/dist/nextjs/api.js.map +1 -0
- package/dist/{adapters/nextjs → nextjs}/server.d.ts +47 -65
- package/dist/nextjs/server.js +179 -0
- package/dist/nextjs/server.js.map +1 -0
- package/dist/server.d.ts +4328 -529
- package/dist/server.js +7841 -1247
- package/dist/server.js.map +1 -1
- package/migrations/{0000_skinny_christian_walker.sql → 0000_mysterious_colossus.sql} +53 -23
- package/migrations/meta/0000_snapshot.json +281 -46
- package/migrations/meta/_journal.json +2 -2
- package/package.json +31 -31
- package/dist/adapters/nextjs/api.d.ts +0 -446
- package/dist/adapters/nextjs/api.js +0 -3279
- package/dist/adapters/nextjs/api.js.map +0 -1
- package/dist/adapters/nextjs/server.js +0 -3645
- package/dist/adapters/nextjs/server.js.map +0 -1
- package/dist/lib/api/auth-codes-verify.d.ts +0 -37
- package/dist/lib/api/auth-codes-verify.js +0 -2949
- package/dist/lib/api/auth-codes-verify.js.map +0 -1
- package/dist/lib/api/auth-codes.d.ts +0 -37
- package/dist/lib/api/auth-codes.js +0 -2949
- package/dist/lib/api/auth-codes.js.map +0 -1
- package/dist/lib/api/auth-exists.d.ts +0 -38
- package/dist/lib/api/auth-exists.js +0 -2949
- package/dist/lib/api/auth-exists.js.map +0 -1
- package/dist/lib/api/auth-invitations-accept.d.ts +0 -38
- package/dist/lib/api/auth-invitations-accept.js +0 -2883
- package/dist/lib/api/auth-invitations-accept.js.map +0 -1
- package/dist/lib/api/auth-invitations-cancel.d.ts +0 -37
- package/dist/lib/api/auth-invitations-cancel.js +0 -2883
- package/dist/lib/api/auth-invitations-cancel.js.map +0 -1
- package/dist/lib/api/auth-invitations-delete.d.ts +0 -36
- package/dist/lib/api/auth-invitations-delete.js +0 -2883
- package/dist/lib/api/auth-invitations-delete.js.map +0 -1
- package/dist/lib/api/auth-invitations-resend.d.ts +0 -37
- package/dist/lib/api/auth-invitations-resend.js +0 -2883
- package/dist/lib/api/auth-invitations-resend.js.map +0 -1
- package/dist/lib/api/auth-invitations.d.ts +0 -109
- package/dist/lib/api/auth-invitations.js +0 -2887
- package/dist/lib/api/auth-invitations.js.map +0 -1
- package/dist/lib/api/auth-keys-rotate.d.ts +0 -37
- package/dist/lib/api/auth-keys-rotate.js +0 -2949
- package/dist/lib/api/auth-keys-rotate.js.map +0 -1
- package/dist/lib/api/auth-login.d.ts +0 -39
- package/dist/lib/api/auth-login.js +0 -2949
- package/dist/lib/api/auth-login.js.map +0 -1
- package/dist/lib/api/auth-logout.d.ts +0 -36
- package/dist/lib/api/auth-logout.js +0 -2949
- package/dist/lib/api/auth-logout.js.map +0 -1
- package/dist/lib/api/auth-me.d.ts +0 -50
- package/dist/lib/api/auth-me.js +0 -2949
- package/dist/lib/api/auth-me.js.map +0 -1
- package/dist/lib/api/auth-password.d.ts +0 -36
- package/dist/lib/api/auth-password.js +0 -2949
- package/dist/lib/api/auth-password.js.map +0 -1
- package/dist/lib/api/auth-register.d.ts +0 -38
- package/dist/lib/api/auth-register.js +0 -2949
- package/dist/lib/api/auth-register.js.map +0 -1
- package/dist/lib/api/index.d.ts +0 -356
- package/dist/lib/api/index.js +0 -3261
- package/dist/lib/api/index.js.map +0 -1
- package/dist/lib/config.d.ts +0 -70
- package/dist/lib/config.js +0 -64
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/contracts/auth.d.ts +0 -302
- package/dist/lib/contracts/auth.js +0 -2951
- package/dist/lib/contracts/auth.js.map +0 -1
- package/dist/lib/contracts/index.d.ts +0 -3
- package/dist/lib/contracts/index.js +0 -3190
- package/dist/lib/contracts/index.js.map +0 -1
- package/dist/lib/contracts/invitation.d.ts +0 -243
- package/dist/lib/contracts/invitation.js +0 -2883
- package/dist/lib/contracts/invitation.js.map +0 -1
- package/dist/lib/crypto.d.ts +0 -76
- package/dist/lib/crypto.js +0 -127
- package/dist/lib/crypto.js.map +0 -1
- package/dist/lib/index.d.ts +0 -4
- package/dist/lib/index.js +0 -313
- package/dist/lib/index.js.map +0 -1
- package/dist/lib/session.d.ts +0 -68
- package/dist/lib/session.js +0 -126
- package/dist/lib/session.js.map +0 -1
- package/dist/lib/types/api.d.ts +0 -45
- package/dist/lib/types/api.js +0 -1
- package/dist/lib/types/api.js.map +0 -1
- package/dist/lib/types/index.d.ts +0 -3
- package/dist/lib/types/index.js +0 -2647
- package/dist/lib/types/index.js.map +0 -1
- package/dist/lib/types/schemas.d.ts +0 -45
- package/dist/lib/types/schemas.js +0 -2647
- package/dist/lib/types/schemas.js.map +0 -1
- package/dist/lib.js +0 -1
- package/dist/lib.js.map +0 -1
- package/dist/plugin.d.ts +0 -12
- package/dist/plugin.js +0 -9083
- package/dist/plugin.js.map +0 -1
- package/dist/server/entities/index.d.ts +0 -11
- package/dist/server/entities/index.js +0 -395
- package/dist/server/entities/index.js.map +0 -1
- package/dist/server/entities/invitations.d.ts +0 -241
- package/dist/server/entities/invitations.js +0 -184
- package/dist/server/entities/invitations.js.map +0 -1
- package/dist/server/entities/permissions.d.ts +0 -196
- package/dist/server/entities/permissions.js +0 -49
- package/dist/server/entities/permissions.js.map +0 -1
- package/dist/server/entities/role-permissions.d.ts +0 -107
- package/dist/server/entities/role-permissions.js +0 -115
- package/dist/server/entities/role-permissions.js.map +0 -1
- package/dist/server/entities/roles.d.ts +0 -196
- package/dist/server/entities/roles.js +0 -50
- package/dist/server/entities/roles.js.map +0 -1
- package/dist/server/entities/schema.d.ts +0 -14
- package/dist/server/entities/schema.js +0 -7
- package/dist/server/entities/schema.js.map +0 -1
- package/dist/server/entities/user-permissions.d.ts +0 -163
- package/dist/server/entities/user-permissions.js +0 -193
- package/dist/server/entities/user-permissions.js.map +0 -1
- package/dist/server/entities/user-public-keys.d.ts +0 -227
- package/dist/server/entities/user-public-keys.js +0 -156
- package/dist/server/entities/user-public-keys.js.map +0 -1
- package/dist/server/entities/user-social-accounts.d.ts +0 -189
- package/dist/server/entities/user-social-accounts.js +0 -149
- package/dist/server/entities/user-social-accounts.js.map +0 -1
- package/dist/server/entities/users.d.ts +0 -235
- package/dist/server/entities/users.js +0 -117
- package/dist/server/entities/users.js.map +0 -1
- package/dist/server/entities/verification-codes.d.ts +0 -191
- package/dist/server/entities/verification-codes.js +0 -49
- package/dist/server/entities/verification-codes.js.map +0 -1
- package/dist/server/routes/auth/index.d.ts +0 -10
- package/dist/server/routes/auth/index.js +0 -4460
- package/dist/server/routes/auth/index.js.map +0 -1
- package/dist/server/routes/index.d.ts +0 -6
- package/dist/server/routes/index.js +0 -6584
- package/dist/server/routes/index.js.map +0 -1
- package/dist/server/routes/invitations/index.d.ts +0 -10
- package/dist/server/routes/invitations/index.js +0 -4395
- package/dist/server/routes/invitations/index.js.map +0 -1
- /package/dist/{lib.d.ts → nextjs/api.d.ts} +0 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
// src/nextjs/api.ts
|
|
2
|
+
import { registerInterceptors } from "@spfn/core/nextjs/server";
|
|
3
|
+
|
|
4
|
+
// src/nextjs/interceptors/login-register.ts
|
|
5
|
+
import { generateKeyPair, sealSession, getSessionTtl, COOKIE_NAMES, authLogger } from "@spfn/auth/server";
|
|
6
|
+
import { env } from "@spfn/core/config";
|
|
7
|
+
var loginRegisterInterceptor = {
|
|
8
|
+
pathPattern: /^\/_auth\/(login|register)$/,
|
|
9
|
+
method: "POST",
|
|
10
|
+
request: async (ctx, next) => {
|
|
11
|
+
const oldKeyId = ctx.cookies.get(COOKIE_NAMES.SESSION_KEY_ID);
|
|
12
|
+
const remember = ctx.body?.remember;
|
|
13
|
+
const keyPair = generateKeyPair("ES256");
|
|
14
|
+
if (!ctx.body) {
|
|
15
|
+
ctx.body = {};
|
|
16
|
+
}
|
|
17
|
+
ctx.body.publicKey = keyPair.publicKey;
|
|
18
|
+
ctx.body.keyId = keyPair.keyId;
|
|
19
|
+
ctx.body.fingerprint = keyPair.fingerprint;
|
|
20
|
+
ctx.body.algorithm = keyPair.algorithm;
|
|
21
|
+
ctx.body.keySize = Buffer.from(keyPair.publicKey, "base64").length;
|
|
22
|
+
if (ctx.path === "/_auth/login" && oldKeyId) {
|
|
23
|
+
ctx.body.oldKeyId = oldKeyId;
|
|
24
|
+
}
|
|
25
|
+
delete ctx.body.remember;
|
|
26
|
+
ctx.metadata.privateKey = keyPair.privateKey;
|
|
27
|
+
ctx.metadata.keyId = keyPair.keyId;
|
|
28
|
+
ctx.metadata.algorithm = keyPair.algorithm;
|
|
29
|
+
ctx.metadata.remember = remember;
|
|
30
|
+
await next();
|
|
31
|
+
},
|
|
32
|
+
response: async (ctx, next) => {
|
|
33
|
+
if (ctx.response.status !== 200) {
|
|
34
|
+
await next();
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const userData = ctx.response.body?.data || ctx.response.body;
|
|
38
|
+
if (!userData?.userId) {
|
|
39
|
+
authLogger.interceptor.login.error("No userId in response");
|
|
40
|
+
await next();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const ttl = getSessionTtl(ctx.metadata.remember);
|
|
45
|
+
const sessionData = {
|
|
46
|
+
userId: userData.userId,
|
|
47
|
+
privateKey: ctx.metadata.privateKey,
|
|
48
|
+
keyId: ctx.metadata.keyId,
|
|
49
|
+
algorithm: ctx.metadata.algorithm
|
|
50
|
+
};
|
|
51
|
+
const sealed = await sealSession(sessionData, ttl);
|
|
52
|
+
ctx.setCookies.push({
|
|
53
|
+
name: COOKIE_NAMES.SESSION,
|
|
54
|
+
value: sealed,
|
|
55
|
+
options: {
|
|
56
|
+
httpOnly: true,
|
|
57
|
+
secure: env.NODE_ENV === "production",
|
|
58
|
+
sameSite: "strict",
|
|
59
|
+
maxAge: ttl,
|
|
60
|
+
path: "/"
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
ctx.setCookies.push({
|
|
64
|
+
name: COOKIE_NAMES.SESSION_KEY_ID,
|
|
65
|
+
value: ctx.metadata.keyId,
|
|
66
|
+
options: {
|
|
67
|
+
httpOnly: true,
|
|
68
|
+
secure: env.NODE_ENV === "production",
|
|
69
|
+
sameSite: "strict",
|
|
70
|
+
maxAge: ttl,
|
|
71
|
+
path: "/"
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
} catch (error) {
|
|
75
|
+
const err = error;
|
|
76
|
+
authLogger.interceptor.login.error("Failed to save session", err);
|
|
77
|
+
}
|
|
78
|
+
await next();
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// src/nextjs/interceptors/general-auth.ts
|
|
83
|
+
import { unsealSession, sealSession as sealSession2, shouldRefreshSession, generateClientToken, getSessionTtl as getSessionTtl2, COOKIE_NAMES as COOKIE_NAMES2, authLogger as authLogger2 } from "@spfn/auth/server";
|
|
84
|
+
import { env as env2 } from "@spfn/core/config";
|
|
85
|
+
function requiresAuth(path) {
|
|
86
|
+
const publicPaths = [
|
|
87
|
+
/^\/_auth\/login$/,
|
|
88
|
+
/^\/_auth\/register$/,
|
|
89
|
+
/^\/_auth\/codes$/,
|
|
90
|
+
// Send verification code
|
|
91
|
+
/^\/_auth\/codes\/verify$/,
|
|
92
|
+
// Verify code
|
|
93
|
+
/^\/_auth\/exists$/
|
|
94
|
+
// Check account exists
|
|
95
|
+
];
|
|
96
|
+
return !publicPaths.some((pattern) => pattern.test(path));
|
|
97
|
+
}
|
|
98
|
+
var generalAuthInterceptor = {
|
|
99
|
+
pathPattern: "*",
|
|
100
|
+
// Match all paths, filter by requiresAuth()
|
|
101
|
+
method: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
102
|
+
request: async (ctx, next) => {
|
|
103
|
+
if (!requiresAuth(ctx.path)) {
|
|
104
|
+
authLogger2.interceptor.general.debug(`Public path, skipping auth: ${ctx.path}`);
|
|
105
|
+
await next();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const cookieNames = Array.from(ctx.cookies.keys());
|
|
109
|
+
authLogger2.interceptor.general.debug("Available cookies:", {
|
|
110
|
+
cookieNames,
|
|
111
|
+
totalCount: cookieNames.length,
|
|
112
|
+
lookingFor: COOKIE_NAMES2.SESSION
|
|
113
|
+
});
|
|
114
|
+
const sessionCookie = ctx.cookies.get(COOKIE_NAMES2.SESSION);
|
|
115
|
+
authLogger2.interceptor.general.debug("Request", {
|
|
116
|
+
method: ctx.method,
|
|
117
|
+
path: ctx.path,
|
|
118
|
+
hasSession: !!sessionCookie,
|
|
119
|
+
sessionCookieValue: sessionCookie ? "***EXISTS***" : "NOT_FOUND"
|
|
120
|
+
});
|
|
121
|
+
if (!sessionCookie) {
|
|
122
|
+
authLogger2.interceptor.general.debug("No session cookie, proceeding without auth");
|
|
123
|
+
await next();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const session = await unsealSession(sessionCookie);
|
|
128
|
+
authLogger2.interceptor.general.debug("Session valid", {
|
|
129
|
+
userId: session.userId,
|
|
130
|
+
keyId: session.keyId
|
|
131
|
+
});
|
|
132
|
+
const needsRefresh = await shouldRefreshSession(sessionCookie, 24);
|
|
133
|
+
if (needsRefresh) {
|
|
134
|
+
authLogger2.interceptor.general.debug("Session needs refresh (within 24h of expiry)");
|
|
135
|
+
ctx.metadata.refreshSession = true;
|
|
136
|
+
ctx.metadata.sessionData = session;
|
|
137
|
+
}
|
|
138
|
+
const token = generateClientToken(
|
|
139
|
+
{
|
|
140
|
+
userId: session.userId,
|
|
141
|
+
keyId: session.keyId,
|
|
142
|
+
timestamp: Date.now()
|
|
143
|
+
},
|
|
144
|
+
session.privateKey,
|
|
145
|
+
session.algorithm,
|
|
146
|
+
{ expiresIn: "15m" }
|
|
147
|
+
);
|
|
148
|
+
authLogger2.interceptor.general.debug("Generated JWT token (expires in 15m)");
|
|
149
|
+
ctx.headers["Authorization"] = `Bearer ${token}`;
|
|
150
|
+
ctx.headers["X-Key-Id"] = session.keyId;
|
|
151
|
+
ctx.metadata.userId = session.userId;
|
|
152
|
+
ctx.metadata.sessionValid = true;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
const err = error;
|
|
155
|
+
if (err.message.includes("expired") || err.message.includes("invalid")) {
|
|
156
|
+
authLogger2.interceptor.general.warn("Session expired or invalid", { message: err.message });
|
|
157
|
+
authLogger2.interceptor.general.debug("Marking session for cleanup");
|
|
158
|
+
ctx.metadata.clearSession = true;
|
|
159
|
+
ctx.metadata.sessionValid = false;
|
|
160
|
+
} else {
|
|
161
|
+
authLogger2.interceptor.general.error("Failed to process session", err);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
await next();
|
|
165
|
+
},
|
|
166
|
+
response: async (ctx, next) => {
|
|
167
|
+
if (ctx.metadata.clearSession) {
|
|
168
|
+
ctx.setCookies.push({
|
|
169
|
+
name: COOKIE_NAMES2.SESSION,
|
|
170
|
+
value: "",
|
|
171
|
+
options: {
|
|
172
|
+
maxAge: 0,
|
|
173
|
+
path: "/"
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
ctx.setCookies.push({
|
|
177
|
+
name: COOKIE_NAMES2.SESSION_KEY_ID,
|
|
178
|
+
value: "",
|
|
179
|
+
options: {
|
|
180
|
+
maxAge: 0,
|
|
181
|
+
path: "/"
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
} else if (ctx.metadata.refreshSession && ctx.response.status === 200) {
|
|
185
|
+
try {
|
|
186
|
+
const sessionData = ctx.metadata.sessionData;
|
|
187
|
+
const ttl = getSessionTtl2();
|
|
188
|
+
const sealed = await sealSession2(sessionData, ttl);
|
|
189
|
+
ctx.setCookies.push({
|
|
190
|
+
name: COOKIE_NAMES2.SESSION,
|
|
191
|
+
value: sealed,
|
|
192
|
+
options: {
|
|
193
|
+
httpOnly: true,
|
|
194
|
+
secure: env2.NODE_ENV === "production",
|
|
195
|
+
sameSite: "strict",
|
|
196
|
+
maxAge: ttl,
|
|
197
|
+
path: "/"
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
ctx.setCookies.push({
|
|
201
|
+
name: COOKIE_NAMES2.SESSION_KEY_ID,
|
|
202
|
+
value: sessionData.keyId,
|
|
203
|
+
options: {
|
|
204
|
+
httpOnly: true,
|
|
205
|
+
secure: process.env.NODE_ENV === "production",
|
|
206
|
+
sameSite: "strict",
|
|
207
|
+
maxAge: ttl,
|
|
208
|
+
path: "/"
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
authLogger2.interceptor.general.info("Session refreshed", { userId: sessionData.userId });
|
|
212
|
+
} catch (error) {
|
|
213
|
+
const err = error;
|
|
214
|
+
authLogger2.interceptor.general.error("Failed to refresh session", err);
|
|
215
|
+
}
|
|
216
|
+
} else if (ctx.path === "/_auth/logout" && ctx.response.status === 200) {
|
|
217
|
+
ctx.setCookies.push({
|
|
218
|
+
name: COOKIE_NAMES2.SESSION,
|
|
219
|
+
value: "",
|
|
220
|
+
options: {
|
|
221
|
+
maxAge: 0,
|
|
222
|
+
path: "/"
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
ctx.setCookies.push({
|
|
226
|
+
name: COOKIE_NAMES2.SESSION_KEY_ID,
|
|
227
|
+
value: "",
|
|
228
|
+
options: {
|
|
229
|
+
maxAge: 0,
|
|
230
|
+
path: "/"
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
await next();
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// src/nextjs/interceptors/key-rotation.ts
|
|
239
|
+
import { generateKeyPair as generateKeyPair2, unsealSession as unsealSession2, sealSession as sealSession3, generateClientToken as generateClientToken2, getSessionTtl as getSessionTtl3, COOKIE_NAMES as COOKIE_NAMES3, authLogger as authLogger3 } from "@spfn/auth/server";
|
|
240
|
+
var keyRotationInterceptor = {
|
|
241
|
+
pathPattern: "/_auth/keys/rotate",
|
|
242
|
+
method: "POST",
|
|
243
|
+
request: async (ctx, next) => {
|
|
244
|
+
const sessionCookie = ctx.cookies.get(COOKIE_NAMES3.SESSION);
|
|
245
|
+
if (!sessionCookie) {
|
|
246
|
+
await next();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
const currentSession = await unsealSession2(sessionCookie);
|
|
251
|
+
const newKeyPair = generateKeyPair2("ES256");
|
|
252
|
+
if (!ctx.body) {
|
|
253
|
+
ctx.body = {};
|
|
254
|
+
}
|
|
255
|
+
ctx.body.publicKey = newKeyPair.publicKey;
|
|
256
|
+
ctx.body.keyId = newKeyPair.keyId;
|
|
257
|
+
ctx.body.fingerprint = newKeyPair.fingerprint;
|
|
258
|
+
ctx.body.algorithm = newKeyPair.algorithm;
|
|
259
|
+
ctx.body.keySize = Buffer.from(newKeyPair.publicKey, "base64").length;
|
|
260
|
+
console.log("New key generated:", newKeyPair);
|
|
261
|
+
console.log("publicKey:", newKeyPair.publicKey);
|
|
262
|
+
console.log("keyId:", newKeyPair.keyId);
|
|
263
|
+
console.log("fingerprint:", newKeyPair.fingerprint);
|
|
264
|
+
const token = generateClientToken2(
|
|
265
|
+
{
|
|
266
|
+
userId: currentSession.userId,
|
|
267
|
+
keyId: currentSession.keyId,
|
|
268
|
+
action: "rotate_key",
|
|
269
|
+
timestamp: Date.now()
|
|
270
|
+
},
|
|
271
|
+
currentSession.privateKey,
|
|
272
|
+
currentSession.algorithm,
|
|
273
|
+
{ expiresIn: "15m" }
|
|
274
|
+
);
|
|
275
|
+
ctx.headers["Authorization"] = `Bearer ${token}`;
|
|
276
|
+
ctx.headers["X-Key-Id"] = currentSession.keyId;
|
|
277
|
+
ctx.metadata.newPrivateKey = newKeyPair.privateKey;
|
|
278
|
+
ctx.metadata.newKeyId = newKeyPair.keyId;
|
|
279
|
+
ctx.metadata.newAlgorithm = newKeyPair.algorithm;
|
|
280
|
+
ctx.metadata.userId = currentSession.userId;
|
|
281
|
+
} catch (error) {
|
|
282
|
+
const err = error;
|
|
283
|
+
authLogger3.interceptor.keyRotation.error("Failed to prepare key rotation", err);
|
|
284
|
+
}
|
|
285
|
+
await next();
|
|
286
|
+
},
|
|
287
|
+
response: async (ctx, next) => {
|
|
288
|
+
if (ctx.response.status !== 200) {
|
|
289
|
+
await next();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (!ctx.metadata.newPrivateKey || !ctx.metadata.userId) {
|
|
293
|
+
authLogger3.interceptor.keyRotation.error("Missing key rotation metadata");
|
|
294
|
+
await next();
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
const ttl = getSessionTtl3();
|
|
299
|
+
const newSessionData = {
|
|
300
|
+
userId: ctx.metadata.userId,
|
|
301
|
+
privateKey: ctx.metadata.newPrivateKey,
|
|
302
|
+
keyId: ctx.metadata.newKeyId,
|
|
303
|
+
algorithm: ctx.metadata.newAlgorithm
|
|
304
|
+
};
|
|
305
|
+
const sealed = await sealSession3(newSessionData, ttl);
|
|
306
|
+
ctx.setCookies.push({
|
|
307
|
+
name: COOKIE_NAMES3.SESSION,
|
|
308
|
+
value: sealed,
|
|
309
|
+
options: {
|
|
310
|
+
httpOnly: true,
|
|
311
|
+
secure: process.env.NODE_ENV === "production",
|
|
312
|
+
sameSite: "strict",
|
|
313
|
+
maxAge: ttl,
|
|
314
|
+
path: "/"
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
ctx.setCookies.push({
|
|
318
|
+
name: COOKIE_NAMES3.SESSION_KEY_ID,
|
|
319
|
+
value: ctx.metadata.newKeyId,
|
|
320
|
+
options: {
|
|
321
|
+
httpOnly: true,
|
|
322
|
+
secure: process.env.NODE_ENV === "production",
|
|
323
|
+
sameSite: "strict",
|
|
324
|
+
maxAge: ttl,
|
|
325
|
+
path: "/"
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
} catch (error) {
|
|
329
|
+
const err = error;
|
|
330
|
+
authLogger3.interceptor.keyRotation.error("Failed to update session after rotation", err);
|
|
331
|
+
}
|
|
332
|
+
await next();
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// src/nextjs/interceptors/index.ts
|
|
337
|
+
var authInterceptors = [
|
|
338
|
+
loginRegisterInterceptor,
|
|
339
|
+
keyRotationInterceptor,
|
|
340
|
+
generalAuthInterceptor
|
|
341
|
+
];
|
|
342
|
+
|
|
343
|
+
// src/nextjs/api.ts
|
|
344
|
+
registerInterceptors("auth", authInterceptors);
|
|
345
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/nextjs/api.ts","../../src/nextjs/interceptors/login-register.ts","../../src/nextjs/interceptors/general-auth.ts","../../src/nextjs/interceptors/key-rotation.ts","../../src/nextjs/interceptors/index.ts"],"sourcesContent":["/**\n * @spfn/auth/adapters/nextjs/api\n *\n * Next.js Adapter for SPFN Auth\n *\n * Provides automatic interceptor registration for seamless auth flow:\n * - Session management (HttpOnly cookies)\n * - JWT generation and signing\n * - Public key encryption\n *\n * @requires next >= 13.0.0\n *\n * @example\n * ```typescript\n * // Just import to auto-register interceptors\n * import '@spfn/auth/nextjs/api';\n * ```\n */\n\n// Re-export interceptors for advanced usage\nimport { registerInterceptors } from \"@spfn/core/nextjs/server\";\nimport { authInterceptors } from './interceptors';\n\n// Auto-register interceptors on import\nregisterInterceptors('auth', authInterceptors);","/**\n * Login/Register Interceptor\n *\n * Automatically handles key generation and session management\n * for login and register endpoints\n */\n\nimport type { InterceptorRule } from '@spfn/core/nextjs';\nimport { generateKeyPair, sealSession, getSessionTtl, COOKIE_NAMES, authLogger } from '@spfn/auth/server';\nimport { env } from '@spfn/core/config';\n\n/**\n * Login and Register Interceptor\n *\n * Request: Generates key pair and adds publicKey to request body\n * Response: Saves privateKey to HttpOnly cookie\n */\nexport const loginRegisterInterceptor: InterceptorRule =\n {\n pathPattern: /^\\/_auth\\/(login|register)$/,\n method: 'POST',\n\n request: async (ctx, next) =>\n {\n // Get old session if exists (for key rotation on login)\n const oldKeyId = ctx.cookies.get(COOKIE_NAMES.SESSION_KEY_ID);\n\n // Extract remember option from request body (if provided)\n const remember = ctx.body?.remember;\n\n // Generate new key pair\n const keyPair = generateKeyPair('ES256');\n\n // Add publicKey data to request body\n if (!ctx.body)\n {\n ctx.body = {};\n }\n\n ctx.body.publicKey = keyPair.publicKey;\n ctx.body.keyId = keyPair.keyId;\n ctx.body.fingerprint = keyPair.fingerprint;\n ctx.body.algorithm = keyPair.algorithm;\n ctx.body.keySize = Buffer.from(keyPair.publicKey, 'base64').length;\n\n // Add oldKeyId for login (key rotation)\n if (ctx.path === '/_auth/login' && oldKeyId)\n {\n ctx.body.oldKeyId = oldKeyId;\n }\n\n // Remove remember from body (not part of contract)\n delete ctx.body.remember;\n\n // Store privateKey and remember in metadata for response interceptor\n ctx.metadata.privateKey = keyPair.privateKey;\n ctx.metadata.keyId = keyPair.keyId;\n ctx.metadata.algorithm = keyPair.algorithm;\n ctx.metadata.remember = remember;\n\n await next();\n },\n\n response: async (ctx, next) =>\n {\n // Only process successful responses\n if (ctx.response.status !== 200)\n {\n await next();\n return;\n }\n\n // Handle both wrapped ({ data: { userId } }) and direct ({ userId }) responses\n const userData = ctx.response.body?.data || ctx.response.body;\n if (!userData?.userId)\n {\n authLogger.interceptor.login.error('No userId in response');\n await next();\n return;\n }\n\n try\n {\n // Get session TTL (priority: runtime > global > env > default)\n const ttl = getSessionTtl(ctx.metadata.remember);\n\n // Encrypt session data\n const sessionData =\n {\n userId: userData.userId,\n privateKey: ctx.metadata.privateKey,\n keyId: ctx.metadata.keyId,\n algorithm: ctx.metadata.algorithm,\n };\n\n const sealed = await sealSession(sessionData, ttl);\n\n // Set HttpOnly session cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: sealed,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n // Set keyId cookie (for oldKeyId lookup)\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: ctx.metadata.keyId,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.login.error('Failed to save session', err);\n }\n\n await next();\n },\n };","/**\n * General Authentication Interceptor\n *\n * Handles authentication for all API requests except login/register\n * - Session validation and renewal\n * - JWT generation and signing\n * - Expired session cleanup\n */\n\nimport type { InterceptorRule } from '@spfn/core/nextjs';\nimport { unsealSession, sealSession, shouldRefreshSession, generateClientToken, getSessionTtl, COOKIE_NAMES, authLogger } from '@spfn/auth/server';\nimport { env } from '@spfn/core/config';\n\n/**\n * Check if path requires authentication\n */\nfunction requiresAuth(path: string): boolean\n{\n // Paths that don't require auth\n const publicPaths = [\n /^\\/_auth\\/login$/,\n /^\\/_auth\\/register$/,\n /^\\/_auth\\/codes$/, // Send verification code\n /^\\/_auth\\/codes\\/verify$/, // Verify code\n /^\\/_auth\\/exists$/, // Check account exists\n ];\n\n return !publicPaths.some((pattern) => pattern.test(path));\n}\n\n/**\n * General Authentication Interceptor\n *\n * Applies to all paths except login/register/codes\n * - Validates session\n * - Generates JWT token\n * - Refreshes session if needed\n * - Clears expired sessions\n */\nexport const generalAuthInterceptor: InterceptorRule =\n{\n pathPattern: '*', // Match all paths, filter by requiresAuth()\n method: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],\n\n request: async (ctx, next) =>\n {\n // Skip if path doesn't require auth\n if (!requiresAuth(ctx.path))\n {\n authLogger.interceptor.general.debug(`Public path, skipping auth: ${ctx.path}`);\n await next();\n return;\n }\n\n // Log available cookies\n const cookieNames = Array.from(ctx.cookies.keys());\n authLogger.interceptor.general.debug('Available cookies:', {\n cookieNames,\n totalCount: cookieNames.length,\n lookingFor: COOKIE_NAMES.SESSION,\n });\n\n const sessionCookie = ctx.cookies.get(COOKIE_NAMES.SESSION);\n\n authLogger.interceptor.general.debug('Request', {\n method: ctx.method,\n path: ctx.path,\n hasSession: !!sessionCookie,\n sessionCookieValue: sessionCookie ? '***EXISTS***' : 'NOT_FOUND',\n });\n\n // No session cookie\n if (!sessionCookie)\n {\n authLogger.interceptor.general.debug('No session cookie, proceeding without auth');\n // Let request proceed - server will return 401\n await next();\n return;\n }\n\n try\n {\n // Decrypt and validate session\n const session = await unsealSession(sessionCookie);\n\n authLogger.interceptor.general.debug('Session valid', {\n userId: session.userId,\n keyId: session.keyId,\n });\n\n // Check if session should be refreshed (within 24h of expiry)\n const needsRefresh = await shouldRefreshSession(sessionCookie, 24);\n\n if (needsRefresh)\n {\n authLogger.interceptor.general.debug('Session needs refresh (within 24h of expiry)');\n // Mark for session renewal in response interceptor\n ctx.metadata.refreshSession = true;\n ctx.metadata.sessionData = session;\n }\n\n // Generate JWT token\n const token = generateClientToken(\n {\n userId: session.userId,\n keyId: session.keyId,\n timestamp: Date.now(),\n },\n session.privateKey,\n session.algorithm,\n { expiresIn: '15m' }\n );\n\n authLogger.interceptor.general.debug('Generated JWT token (expires in 15m)');\n\n // Add authentication headers\n ctx.headers['Authorization'] = `Bearer ${token}`;\n ctx.headers['X-Key-Id'] = session.keyId;\n\n // Store session info in metadata\n ctx.metadata.userId = session.userId;\n ctx.metadata.sessionValid = true;\n }\n catch (error)\n {\n const err = error as Error;\n\n // Session expired or invalid\n if (err.message.includes('expired') || err.message.includes('invalid'))\n {\n authLogger.interceptor.general.warn('Session expired or invalid', { message: err.message });\n authLogger.interceptor.general.debug('Marking session for cleanup');\n\n // Mark for cleanup in response interceptor\n ctx.metadata.clearSession = true;\n ctx.metadata.sessionValid = false;\n }\n else\n {\n authLogger.interceptor.general.error('Failed to process session', err);\n }\n }\n\n await next();\n },\n\n response: async (ctx, next) =>\n {\n // Clear expired/invalid session\n if (ctx.metadata.clearSession)\n {\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n }\n // Refresh session if needed and request was successful\n else if (ctx.metadata.refreshSession && ctx.response.status === 200)\n {\n try\n {\n const sessionData = ctx.metadata.sessionData;\n const ttl = getSessionTtl();\n\n // Re-encrypt session with new TTL\n const sealed = await sealSession(sessionData, ttl);\n\n // Update session cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: sealed,\n options: {\n httpOnly: true,\n secure: env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n // Update keyId cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: sessionData.keyId,\n options: {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n authLogger.interceptor.general.info('Session refreshed', { userId: sessionData.userId });\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.general.error('Failed to refresh session', err);\n }\n }\n // Handle logout (clear session)\n else if (ctx.path === '/_auth/logout' && ctx.response.status === 200)\n {\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: '',\n options: {\n maxAge: 0,\n path: '/',\n },\n });\n }\n\n await next();\n },\n};","/**\n * Key Rotation Interceptor\n *\n * Handles key rotation with new key generation and session update\n */\n\nimport type { InterceptorRule } from '@spfn/core/nextjs';\nimport { generateKeyPair, unsealSession, sealSession, generateClientToken, getSessionTtl, COOKIE_NAMES, authLogger } from '@spfn/auth/server';\n\n/**\n * Key Rotation Interceptor\n *\n * Request: Generates new key pair and adds to body, authenticates with current key\n * Response: Updates session with new privateKey\n */\nexport const keyRotationInterceptor: InterceptorRule =\n{\n pathPattern: '/_auth/keys/rotate',\n method: 'POST',\n\n request: async (ctx, next) =>\n {\n const sessionCookie = ctx.cookies.get(COOKIE_NAMES.SESSION);\n\n if (!sessionCookie)\n {\n await next();\n return;\n }\n\n try\n {\n // Get current session\n const currentSession = await unsealSession(sessionCookie);\n\n // Generate new key pair\n const newKeyPair = generateKeyPair('ES256');\n\n // Add new publicKey to request body\n if (!ctx.body)\n {\n ctx.body = {};\n }\n\n ctx.body.publicKey = newKeyPair.publicKey;\n ctx.body.keyId = newKeyPair.keyId;\n ctx.body.fingerprint = newKeyPair.fingerprint;\n ctx.body.algorithm = newKeyPair.algorithm;\n ctx.body.keySize = Buffer.from(newKeyPair.publicKey, 'base64').length;\n\n console.log('New key generated:', newKeyPair);\n console.log('publicKey:', newKeyPair.publicKey);\n console.log('keyId:', newKeyPair.keyId);\n console.log('fingerprint:', newKeyPair.fingerprint);\n\n // Authenticate with CURRENT key\n const token = generateClientToken(\n {\n userId: currentSession.userId,\n keyId: currentSession.keyId,\n action: 'rotate_key',\n timestamp: Date.now(),\n },\n currentSession.privateKey,\n currentSession.algorithm,\n {expiresIn: '15m'}\n );\n\n ctx.headers['Authorization'] = `Bearer ${token}`;\n ctx.headers['X-Key-Id'] = currentSession.keyId;\n\n // Store new key and userId in metadata\n ctx.metadata.newPrivateKey = newKeyPair.privateKey;\n ctx.metadata.newKeyId = newKeyPair.keyId;\n ctx.metadata.newAlgorithm = newKeyPair.algorithm;\n ctx.metadata.userId = currentSession.userId;\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.keyRotation.error('Failed to prepare key rotation', err);\n }\n\n await next();\n },\n\n response: async (ctx, next) =>\n {\n // Only update session on successful rotation\n if (ctx.response.status !== 200)\n {\n await next();\n return;\n }\n\n if (!ctx.metadata.newPrivateKey || !ctx.metadata.userId)\n {\n authLogger.interceptor.keyRotation.error('Missing key rotation metadata');\n await next();\n return;\n }\n\n try\n {\n // Get session TTL\n const ttl = getSessionTtl();\n\n // Create new session with rotated key\n const newSessionData =\n {\n userId: ctx.metadata.userId,\n privateKey: ctx.metadata.newPrivateKey,\n keyId: ctx.metadata.newKeyId,\n algorithm: ctx.metadata.newAlgorithm,\n };\n\n const sealed = await sealSession(newSessionData, ttl);\n\n // Update session cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION,\n value: sealed,\n options: {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n\n // Update keyId cookie\n ctx.setCookies.push({\n name: COOKIE_NAMES.SESSION_KEY_ID,\n value: ctx.metadata.newKeyId,\n options: {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: ttl,\n path: '/',\n },\n });\n }\n catch (error)\n {\n const err = error as Error;\n authLogger.interceptor.keyRotation.error('Failed to update session after rotation', err);\n }\n\n await next();\n },\n};","/**\n * Auth Interceptors for Next.js Proxy\n *\n * Automatically registers interceptors for authentication flow\n *\n * Order matters - more specific interceptors first:\n * 1. loginRegisterInterceptor - Most specific (login/register only)\n * 2. keyRotationInterceptor - Specific (key rotation only)\n * 3. generalAuthInterceptor - General (all authenticated requests)\n */\n\nimport { loginRegisterInterceptor } from './login-register';\nimport { generalAuthInterceptor } from './general-auth';\nimport { keyRotationInterceptor } from './key-rotation';\n\n/**\n * All auth interceptors\n *\n * Execution order:\n * 1. loginRegisterInterceptor - Handles login/register (key generation + session save)\n * 2. keyRotationInterceptor - Handles key rotation (new key generation + session update)\n * 3. generalAuthInterceptor - Handles all authenticated requests (session validation + JWT injection + session renewal)\n */\nexport const authInterceptors = [\n loginRegisterInterceptor,\n keyRotationInterceptor,\n generalAuthInterceptor,\n];\n\nexport { loginRegisterInterceptor } from './login-register';\nexport { generalAuthInterceptor } from './general-auth';\nexport { keyRotationInterceptor } from './key-rotation';\n\n// Deprecated: use generalAuthInterceptor instead\nexport { generalAuthInterceptor as authenticationInterceptor };"],"mappings":"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","names":["sealSession","getSessionTtl","COOKIE_NAMES","authLogger","env","generateKeyPair","unsealSession","sealSession","generateClientToken","getSessionTtl","COOKIE_NAMES","authLogger"]}
|
|
@@ -1,67 +1,7 @@
|
|
|
1
|
-
import * as _spfn_core_client_nextjs from '@spfn/core/client/nextjs';
|
|
2
|
-
import { InterceptorRule } from '@spfn/core/client/nextjs';
|
|
3
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
2
|
import { ReactNode } from 'react';
|
|
5
|
-
import { SessionData } from '
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* General Authentication Interceptor
|
|
9
|
-
*
|
|
10
|
-
* Handles authentication for all API requests except login/register
|
|
11
|
-
* - Session validation and renewal
|
|
12
|
-
* - JWT generation and signing
|
|
13
|
-
* - Expired session cleanup
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* General Authentication Interceptor
|
|
18
|
-
*
|
|
19
|
-
* Applies to all /_auth/* paths except login/register/codes
|
|
20
|
-
* - Validates session
|
|
21
|
-
* - Generates JWT token
|
|
22
|
-
* - Refreshes session if needed
|
|
23
|
-
* - Clears expired sessions
|
|
24
|
-
*/
|
|
25
|
-
declare const generalAuthInterceptor: InterceptorRule;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Login/Register Interceptor
|
|
29
|
-
*
|
|
30
|
-
* Automatically handles key generation and session management
|
|
31
|
-
* for login and register endpoints
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Login and Register Interceptor
|
|
36
|
-
*
|
|
37
|
-
* Request: Generates key pair and adds publicKey to request body
|
|
38
|
-
* Response: Saves privateKey to HttpOnly cookie
|
|
39
|
-
*/
|
|
40
|
-
declare const loginRegisterInterceptor: InterceptorRule;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Key Rotation Interceptor
|
|
44
|
-
*
|
|
45
|
-
* Handles key rotation with new key generation and session update
|
|
46
|
-
*/
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Key Rotation Interceptor
|
|
50
|
-
*
|
|
51
|
-
* Request: Generates new key pair and adds to body, authenticates with current key
|
|
52
|
-
* Response: Updates session with new privateKey
|
|
53
|
-
*/
|
|
54
|
-
declare const keyRotationInterceptor: InterceptorRule;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* All auth interceptors
|
|
58
|
-
*
|
|
59
|
-
* Execution order:
|
|
60
|
-
* 1. loginRegisterInterceptor - Handles login/register (key generation + session save)
|
|
61
|
-
* 2. keyRotationInterceptor - Handles key rotation (new key generation + session update)
|
|
62
|
-
* 3. generalAuthInterceptor - Handles all authenticated requests (session validation + JWT injection + session renewal)
|
|
63
|
-
*/
|
|
64
|
-
declare const authInterceptors: _spfn_core_client_nextjs.InterceptorRule[];
|
|
3
|
+
import { SessionData } from '@spfn/auth/server';
|
|
4
|
+
export { SessionData } from '@spfn/auth/server';
|
|
65
5
|
|
|
66
6
|
interface RequireAuthProps {
|
|
67
7
|
/**
|
|
@@ -196,7 +136,7 @@ declare function RequirePermission({ permissions, children, redirectTo, fallback
|
|
|
196
136
|
/**
|
|
197
137
|
* Server-side auth utilities for guards
|
|
198
138
|
*
|
|
199
|
-
* Uses
|
|
139
|
+
* Uses authApi to check permissions in real-time
|
|
200
140
|
*/
|
|
201
141
|
/**
|
|
202
142
|
* Get user role
|
|
@@ -228,10 +168,52 @@ interface PublicSession {
|
|
|
228
168
|
/** User ID */
|
|
229
169
|
userId: string;
|
|
230
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Options for saveSession
|
|
173
|
+
*/
|
|
174
|
+
interface SaveSessionOptions {
|
|
175
|
+
/**
|
|
176
|
+
* Session TTL (time to live)
|
|
177
|
+
*
|
|
178
|
+
* Supports:
|
|
179
|
+
* - Number: seconds (e.g., 2592000)
|
|
180
|
+
* - String: duration format ('30d', '12h', '45m', '3600s')
|
|
181
|
+
*
|
|
182
|
+
* If not provided, uses global configuration:
|
|
183
|
+
* 1. Global config (configureAuth)
|
|
184
|
+
* 2. Environment variable (SPFN_AUTH_SESSION_TTL)
|
|
185
|
+
* 3. Default (7d)
|
|
186
|
+
*/
|
|
187
|
+
maxAge?: number | string;
|
|
188
|
+
/**
|
|
189
|
+
* Remember me option
|
|
190
|
+
*
|
|
191
|
+
* When true, uses extended session duration (if configured)
|
|
192
|
+
*/
|
|
193
|
+
remember?: boolean;
|
|
194
|
+
}
|
|
231
195
|
/**
|
|
232
196
|
* Save session to HttpOnly cookie
|
|
197
|
+
*
|
|
198
|
+
* @param data - Session data to save
|
|
199
|
+
* @param options - Session options (maxAge, remember)
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* ```typescript
|
|
203
|
+
* // Use global configuration
|
|
204
|
+
* await saveSession(sessionData);
|
|
205
|
+
*
|
|
206
|
+
* // Custom TTL with duration string
|
|
207
|
+
* await saveSession(sessionData, { maxAge: '30d' });
|
|
208
|
+
*
|
|
209
|
+
* // Custom TTL in seconds
|
|
210
|
+
* await saveSession(sessionData, { maxAge: 2592000 });
|
|
211
|
+
*
|
|
212
|
+
* // Remember me
|
|
213
|
+
* await saveSession(sessionData, { remember: true });
|
|
214
|
+
* ```
|
|
233
215
|
*/
|
|
234
|
-
declare function saveSession(data: SessionData,
|
|
216
|
+
declare function saveSession(data: SessionData, options?: SaveSessionOptions): Promise<void>;
|
|
235
217
|
/**
|
|
236
218
|
* Get session from HttpOnly cookie
|
|
237
219
|
*
|
|
@@ -243,4 +225,4 @@ declare function getSession(): Promise<PublicSession | null>;
|
|
|
243
225
|
*/
|
|
244
226
|
declare function clearSession(): Promise<void>;
|
|
245
227
|
|
|
246
|
-
export { type PublicSession, RequireAuth, type RequireAuthProps, RequirePermission, type RequirePermissionProps, RequireRole, type RequireRoleProps,
|
|
228
|
+
export { type PublicSession, RequireAuth, type RequireAuthProps, RequirePermission, type RequirePermissionProps, RequireRole, type RequireRoleProps, type SaveSessionOptions, clearSession, getSession, getUserPermissions, getUserRole, hasAnyPermission, hasAnyRole, saveSession };
|