@stacksjs/auth 0.70.255 → 0.70.257
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/authentication.js +5 -5
- package/dist/email-verification.js +4 -4
- package/dist/passkey.js +3 -3
- package/dist/password/reset.js +2 -2
- package/dist/session-auth.js +6 -6
- package/dist/tokens.d.ts +1 -1
- package/dist/tokens.js +40 -38
- package/dist/two-factor.js +5 -5
- package/package.json +3 -3
package/dist/authentication.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { config } from "@stacksjs/config";
|
|
2
|
-
import { db } from "@stacksjs/database";
|
|
2
|
+
import { db, parseSqlDateTime } from "@stacksjs/database";
|
|
3
3
|
import { HttpError } from "@stacksjs/error-handling";
|
|
4
4
|
import { formatDate, User } from "@stacksjs/orm";
|
|
5
5
|
import { getCurrentRequest, request } from "@stacksjs/router";
|
|
@@ -110,7 +110,7 @@ export class Auth {
|
|
|
110
110
|
name: token.name || "auth-token",
|
|
111
111
|
scopes: parseScopes(token.scopes),
|
|
112
112
|
abilities: parseScopes(token.scopes),
|
|
113
|
-
expiresAt:
|
|
113
|
+
expiresAt: parseSqlDateTime(token.expires_at),
|
|
114
114
|
createdAt: token.created_at ? new Date(String(token.created_at)) : new Date,
|
|
115
115
|
updatedAt: token.updated_at ? new Date(String(token.updated_at)) : new Date,
|
|
116
116
|
revoked: !!token.revoked
|
|
@@ -243,7 +243,7 @@ export class Auth {
|
|
|
243
243
|
if (!accessToken)
|
|
244
244
|
return !1;
|
|
245
245
|
log.debug(`[auth] Token validated for token#${accessToken.id}`);
|
|
246
|
-
if (accessToken.expires_at &&
|
|
246
|
+
if (accessToken.expires_at && (parseSqlDateTime(accessToken.expires_at) ?? new Date(0)) < new Date) {
|
|
247
247
|
await db.deleteFrom("oauth_access_tokens").where("id", "=", accessToken.id).execute();
|
|
248
248
|
return !1;
|
|
249
249
|
}
|
|
@@ -258,7 +258,7 @@ export class Auth {
|
|
|
258
258
|
const hashedPlainToken = hashToken(token), accessToken = await db.selectFrom("oauth_access_tokens").where("token", "=", hashedPlainToken).selectAll().executeTakeFirst();
|
|
259
259
|
if (!accessToken)
|
|
260
260
|
return;
|
|
261
|
-
if (accessToken.expires_at &&
|
|
261
|
+
if (accessToken.expires_at && (parseSqlDateTime(accessToken.expires_at) ?? new Date(0)) < new Date) {
|
|
262
262
|
await db.deleteFrom("oauth_access_tokens").where("id", "=", accessToken.id).execute();
|
|
263
263
|
return;
|
|
264
264
|
}
|
|
@@ -331,7 +331,7 @@ export class Auth {
|
|
|
331
331
|
name: String(token.name || "auth-token"),
|
|
332
332
|
scopes: parseScopes(String(token.scopes ?? "")),
|
|
333
333
|
abilities: parseScopes(String(token.scopes ?? "")),
|
|
334
|
-
expiresAt:
|
|
334
|
+
expiresAt: parseSqlDateTime(token.expires_at),
|
|
335
335
|
createdAt: token.created_at ? new Date(String(token.created_at)) : new Date,
|
|
336
336
|
updatedAt: token.updated_at ? new Date(String(token.updated_at)) : new Date,
|
|
337
337
|
revoked: !!token.revoked
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
2
|
import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
3
3
|
import { config } from "@stacksjs/config";
|
|
4
|
-
import { db } from "@stacksjs/database";
|
|
4
|
+
import { db, sqlDateTime, parseSqlDateTime } from "@stacksjs/database";
|
|
5
5
|
import { mail, template } from "@stacksjs/email";
|
|
6
6
|
import { log } from "@stacksjs/logging";
|
|
7
7
|
function getVerificationKey() {
|
|
@@ -42,7 +42,7 @@ export async function sendVerificationEmail(user) {
|
|
|
42
42
|
await db.insertInto("email_verifications").values({
|
|
43
43
|
user_id: user.id,
|
|
44
44
|
token: hash,
|
|
45
|
-
expires_at: expiresAt
|
|
45
|
+
expires_at: sqlDateTime(expiresAt)
|
|
46
46
|
}).executeTakeFirst();
|
|
47
47
|
const verificationUrl = getVerificationUrl(user.id, token), appName = config.app.name || "Stacks";
|
|
48
48
|
try {
|
|
@@ -78,14 +78,14 @@ export async function verifyEmail(userId, token) {
|
|
|
78
78
|
const record = await db.selectFrom("email_verifications").where("user_id", "=", userId).selectAll().executeTakeFirst();
|
|
79
79
|
if (!record)
|
|
80
80
|
return { success: !1, message: "No verification request found. Please request a new verification email." };
|
|
81
|
-
const expiresAt = new Date(
|
|
81
|
+
const expiresAt = parseSqlDateTime(record.expires_at) ?? new Date(Number.NaN);
|
|
82
82
|
if (Number.isNaN(expiresAt.getTime()) || new Date > expiresAt) {
|
|
83
83
|
await db.deleteFrom("email_verifications").where("user_id", "=", userId).execute();
|
|
84
84
|
return { success: !1, message: "Verification link has expired. Please request a new one." };
|
|
85
85
|
}
|
|
86
86
|
if (!verifyToken(userId, token, record.token))
|
|
87
87
|
return { success: !1, message: "Invalid verification link." };
|
|
88
|
-
await db.updateTable("users").set({ email_verified_at:
|
|
88
|
+
await db.updateTable("users").set({ email_verified_at: sqlDateTime() }).where("id", "=", userId).executeTakeFirst();
|
|
89
89
|
await db.deleteFrom("email_verifications").where("user_id", "=", userId).execute();
|
|
90
90
|
return { success: !0, message: "Email verified successfully." };
|
|
91
91
|
}
|
package/dist/passkey.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
|
-
import { db } from "@stacksjs/database";
|
|
2
|
+
import { db, sqlDateTime, parseSqlDateTime } from "@stacksjs/database";
|
|
3
3
|
import { User } from "@stacksjs/orm";
|
|
4
4
|
export {
|
|
5
5
|
generateRegistrationOptions,
|
|
@@ -55,7 +55,7 @@ function formatDateTime() {
|
|
|
55
55
|
}
|
|
56
56
|
const DEFAULT_CHALLENGE_TTL_SECONDS = 300;
|
|
57
57
|
export async function storeWebAuthnChallenge(userId, challenge, purpose, ttlSeconds = DEFAULT_CHALLENGE_TTL_SECONDS) {
|
|
58
|
-
const expiresAt = new Date(Date.now() + ttlSeconds * 1000)
|
|
58
|
+
const expiresAt = sqlDateTime(new Date(Date.now() + ttlSeconds * 1000)), encodedChallenge = typeof challenge === "string" ? challenge : Buffer.from(challenge).toString("base64url");
|
|
59
59
|
await db.deleteFrom("webauthn_challenges").where("user_id", "=", userId).where("purpose", "=", purpose).execute();
|
|
60
60
|
await db.insertInto("webauthn_challenges").values({
|
|
61
61
|
user_id: userId,
|
|
@@ -69,7 +69,7 @@ export async function consumeWebAuthnChallenge(userId, purpose) {
|
|
|
69
69
|
if (!row)
|
|
70
70
|
return null;
|
|
71
71
|
await db.deleteFrom("webauthn_challenges").where("user_id", "=", userId).where("purpose", "=", purpose).execute();
|
|
72
|
-
const expiresAt =
|
|
72
|
+
const expiresAt = parseSqlDateTime(row.expires_at)?.getTime() ?? 0;
|
|
73
73
|
if (Date.now() > expiresAt)
|
|
74
74
|
return null;
|
|
75
75
|
return Buffer.from(String(row.challenge), "base64url");
|
package/dist/password/reset.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { config } from "@stacksjs/config";
|
|
3
|
-
import { db } from "@stacksjs/database";
|
|
3
|
+
import { db, sqlDateTime } from "@stacksjs/database";
|
|
4
4
|
import { mail, template } from "@stacksjs/email";
|
|
5
5
|
import { log } from "@stacksjs/logging";
|
|
6
6
|
import { formatDate } from "@stacksjs/orm";
|
|
@@ -57,7 +57,7 @@ export function passwordResets(email) {
|
|
|
57
57
|
return randomBytes(32).toString("hex");
|
|
58
58
|
}
|
|
59
59
|
async function createResetToken() {
|
|
60
|
-
const token = generateResetToken(), hashedToken = await makeHash(token, { algorithm: "bcrypt" }), expireMinutes = getTokenExpireMinutes(), expiresAt = new Date(Date.now() + expireMinutes * 60000)
|
|
60
|
+
const token = generateResetToken(), hashedToken = await makeHash(token, { algorithm: "bcrypt" }), expireMinutes = getTokenExpireMinutes(), expiresAt = sqlDateTime(new Date(Date.now() + expireMinutes * 60000));
|
|
61
61
|
await db.deleteFrom("password_resets").where("email", "=", email).execute();
|
|
62
62
|
await db.insertInto("password_resets").values({
|
|
63
63
|
email,
|
package/dist/session-auth.js
CHANGED
|
@@ -3,7 +3,7 @@ import { log } from "@stacksjs/logging";
|
|
|
3
3
|
import { User } from "@stacksjs/orm";
|
|
4
4
|
import { verifyHash } from "@stacksjs/security";
|
|
5
5
|
import { config } from "@stacksjs/config";
|
|
6
|
-
import { db } from "@stacksjs/database";
|
|
6
|
+
import { db, sqlDateTime, parseSqlDateTime } from "@stacksjs/database";
|
|
7
7
|
import { getCurrentRequest } from "@stacksjs/router";
|
|
8
8
|
import { DUMMY_BCRYPT_HASH } from "./internal-constants";
|
|
9
9
|
import { RateLimiter } from "./rate-limiter";
|
|
@@ -57,7 +57,7 @@ export async function sessionLogin(email, password, fingerprint) {
|
|
|
57
57
|
user_agent: userAgent,
|
|
58
58
|
payload: "{}",
|
|
59
59
|
last_activity: Math.floor(Date.now() / 1000),
|
|
60
|
-
expires_at: expiresAt
|
|
60
|
+
expires_at: sqlDateTime(expiresAt)
|
|
61
61
|
}).execute();
|
|
62
62
|
} catch (err) {
|
|
63
63
|
log.error(`[auth] Session persistence failed for user#${user.id}: ${err.message}`);
|
|
@@ -88,7 +88,7 @@ export async function sessionUser(sessionId) {
|
|
|
88
88
|
const session = await db.selectFrom("sessions").where("id", "=", sessionId).selectAll().executeTakeFirst();
|
|
89
89
|
if (!session)
|
|
90
90
|
return;
|
|
91
|
-
const expiresAt =
|
|
91
|
+
const expiresAt = parseSqlDateTime(session.expires_at)?.getTime() ?? 0;
|
|
92
92
|
if (Date.now() > expiresAt) {
|
|
93
93
|
await db.deleteFrom("sessions").where("id", "=", sessionId).execute();
|
|
94
94
|
return;
|
|
@@ -107,7 +107,7 @@ export async function sessionCheck(sessionId) {
|
|
|
107
107
|
const session = await db.selectFrom("sessions").where("id", "=", sessionId).selectAll().executeTakeFirst();
|
|
108
108
|
if (!session)
|
|
109
109
|
return !1;
|
|
110
|
-
const expiresAt =
|
|
110
|
+
const expiresAt = parseSqlDateTime(session.expires_at)?.getTime() ?? 0;
|
|
111
111
|
if (Date.now() > expiresAt) {
|
|
112
112
|
await db.deleteFrom("sessions").where("id", "=", sessionId).execute();
|
|
113
113
|
return !1;
|
|
@@ -124,7 +124,7 @@ export async function sessionRefresh(sessionId, ttlMs = 86400000) {
|
|
|
124
124
|
const session = await db.selectFrom("sessions").where("id", "=", sessionId).selectAll().executeTakeFirst();
|
|
125
125
|
if (!session)
|
|
126
126
|
return !1;
|
|
127
|
-
const expiresAt =
|
|
127
|
+
const expiresAt = parseSqlDateTime(session.expires_at)?.getTime() ?? 0;
|
|
128
128
|
if (Date.now() > expiresAt) {
|
|
129
129
|
await db.deleteFrom("sessions").where("id", "=", sessionId).execute();
|
|
130
130
|
return !1;
|
|
@@ -133,7 +133,7 @@ export async function sessionRefresh(sessionId, ttlMs = 86400000) {
|
|
|
133
133
|
return !1;
|
|
134
134
|
const newExpiry = new Date(Date.now() + ttlMs);
|
|
135
135
|
await db.updateTable("sessions").set({
|
|
136
|
-
expires_at: newExpiry
|
|
136
|
+
expires_at: sqlDateTime(newExpiry),
|
|
137
137
|
last_activity: Math.floor(Date.now() / 1000)
|
|
138
138
|
}).where("id", "=", sessionId).execute();
|
|
139
139
|
return !0;
|
package/dist/tokens.d.ts
CHANGED
|
@@ -273,7 +273,7 @@ export declare function parseScopes(scopes: string | string[] | null | undefined
|
|
|
273
273
|
/** Current database driver */
|
|
274
274
|
declare const dbDriver: DatabaseDriver;
|
|
275
275
|
/** Cross-database SQL helpers */
|
|
276
|
-
declare const sql:
|
|
276
|
+
declare const sql: ReturnType<typeof sqlHelpers>;
|
|
277
277
|
/**
|
|
278
278
|
* Alias for currentAccessToken
|
|
279
279
|
*
|
package/dist/tokens.js
CHANGED
|
@@ -4,8 +4,11 @@ import { db } from "@stacksjs/database";
|
|
|
4
4
|
import { HttpError } from "@stacksjs/error-handling";
|
|
5
5
|
import { getCurrentRequest } from "@stacksjs/router";
|
|
6
6
|
import { env } from "@stacksjs/env";
|
|
7
|
-
import { sqlHelpers } from "@stacksjs/database";
|
|
7
|
+
import { parseSqlDateTime, sqlDateTime, sqlDateTimeLiteral, sqlHelpers } from "@stacksjs/database";
|
|
8
8
|
const dbDriver = env.DB_CONNECTION || "sqlite", sql = sqlHelpers(dbDriver), { isPostgres, isMysql, now, boolTrue, boolFalse } = sql;
|
|
9
|
+
function appNow() {
|
|
10
|
+
return sqlDateTimeLiteral();
|
|
11
|
+
}
|
|
9
12
|
function param(index) {
|
|
10
13
|
return sql.param(index);
|
|
11
14
|
}
|
|
@@ -28,8 +31,7 @@ export async function getPasswordChangedAt(userId, q = db) {
|
|
|
28
31
|
`, [userId]))[0]?.password_changed_at;
|
|
29
32
|
if (value === null || value === void 0)
|
|
30
33
|
return null;
|
|
31
|
-
|
|
32
|
-
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
34
|
+
return parseSqlDateTime(value);
|
|
33
35
|
} catch {
|
|
34
36
|
return null;
|
|
35
37
|
}
|
|
@@ -39,8 +41,8 @@ export function isIssuedBeforePasswordChange(createdAt, changedAt) {
|
|
|
39
41
|
return !1;
|
|
40
42
|
if (createdAt === null || createdAt === void 0)
|
|
41
43
|
return !1;
|
|
42
|
-
const created =
|
|
43
|
-
if (
|
|
44
|
+
const created = parseSqlDateTime(createdAt);
|
|
45
|
+
if (!created)
|
|
44
46
|
return !1;
|
|
45
47
|
return created.getTime() < changedAt.getTime();
|
|
46
48
|
}
|
|
@@ -69,7 +71,7 @@ export async function findToken(plainTextToken) {
|
|
|
69
71
|
SELECT * FROM oauth_access_tokens
|
|
70
72
|
WHERE token = ${param(1)}
|
|
71
73
|
AND revoked = ${boolFalse}
|
|
72
|
-
AND (expires_at IS NULL OR expires_at > ${
|
|
74
|
+
AND (expires_at IS NULL OR expires_at > ${appNow()})
|
|
73
75
|
LIMIT 1
|
|
74
76
|
`, [hashedToken]))[0];
|
|
75
77
|
if (!row)
|
|
@@ -149,13 +151,13 @@ export async function createToken(userId, name = "access-token", scopes = ["*"],
|
|
|
149
151
|
if (isPostgres)
|
|
150
152
|
await db.unsafe(`
|
|
151
153
|
INSERT INTO oauth_access_tokens (user_id, oauth_client_id, token, name, scopes, revoked, expires_at, created_at, updated_at)
|
|
152
|
-
VALUES ($1, $2, $3, $4, $5, false, $6,
|
|
153
|
-
`, [userId, client.id, hashedToken, name, JSON.stringify(scopes), expiresAt
|
|
154
|
+
VALUES ($1, $2, $3, $4, $5, false, $6, ${appNow()}, ${appNow()})
|
|
155
|
+
`, [userId, client.id, hashedToken, name, JSON.stringify(scopes), sqlDateTime(expiresAt)]);
|
|
154
156
|
else
|
|
155
157
|
await db.unsafe(`
|
|
156
158
|
INSERT INTO oauth_access_tokens (user_id, oauth_client_id, token, name, scopes, revoked, expires_at, created_at, updated_at)
|
|
157
|
-
VALUES (?, ?, ?, ?, ?, 0, ?, ${
|
|
158
|
-
`, [userId, client.id, hashedToken, name, JSON.stringify(scopes), expiresAt
|
|
159
|
+
VALUES (?, ?, ?, ?, ?, 0, ?, ${appNow()}, ${appNow()})
|
|
160
|
+
`, [userId, client.id, hashedToken, name, JSON.stringify(scopes), sqlDateTime(expiresAt)]);
|
|
159
161
|
const row = (await db.unsafe(`
|
|
160
162
|
SELECT * FROM oauth_access_tokens WHERE token = ${param(1)} LIMIT 1
|
|
161
163
|
`, [hashedToken]))[0];
|
|
@@ -180,13 +182,13 @@ export async function createToken(userId, name = "access-token", scopes = ["*"],
|
|
|
180
182
|
if (isPostgres)
|
|
181
183
|
await db.unsafe(`
|
|
182
184
|
INSERT INTO oauth_refresh_tokens (access_token_id, token, revoked, expires_at, created_at)
|
|
183
|
-
VALUES ($1, $2, false, $3,
|
|
184
|
-
`, [accessToken.id, hashedRefreshToken, refreshExpiresAt
|
|
185
|
+
VALUES ($1, $2, false, $3, ${appNow()})
|
|
186
|
+
`, [accessToken.id, hashedRefreshToken, sqlDateTime(refreshExpiresAt)]);
|
|
185
187
|
else
|
|
186
188
|
await db.unsafe(`
|
|
187
189
|
INSERT INTO oauth_refresh_tokens (access_token_id, token, revoked, expires_at, created_at)
|
|
188
|
-
VALUES (?, ?, 0, ?, ${
|
|
189
|
-
`, [accessToken.id, hashedRefreshToken, refreshExpiresAt
|
|
190
|
+
VALUES (?, ?, 0, ?, ${appNow()})
|
|
191
|
+
`, [accessToken.id, hashedRefreshToken, sqlDateTime(refreshExpiresAt)]);
|
|
190
192
|
}
|
|
191
193
|
return {
|
|
192
194
|
accessToken,
|
|
@@ -207,7 +209,7 @@ export async function refreshToken(refreshTokenPlain, options = {}) {
|
|
|
207
209
|
JOIN oauth_access_tokens t ON r.access_token_id = t.id
|
|
208
210
|
WHERE r.token = ${param(1)}
|
|
209
211
|
AND r.revoked = ${boolFalse}
|
|
210
|
-
AND (r.expires_at IS NULL OR r.expires_at > ${
|
|
212
|
+
AND (r.expires_at IS NULL OR r.expires_at > ${appNow()})
|
|
211
213
|
LIMIT 1${forUpdate}
|
|
212
214
|
`, [hashedRefreshToken]))[0];
|
|
213
215
|
if (!refreshRow)
|
|
@@ -229,13 +231,13 @@ export async function refreshToken(refreshTokenPlain, options = {}) {
|
|
|
229
231
|
if (isPostgres)
|
|
230
232
|
await trx.unsafe(`
|
|
231
233
|
INSERT INTO oauth_access_tokens (user_id, oauth_client_id, token, name, scopes, revoked, expires_at, created_at, updated_at)
|
|
232
|
-
VALUES ($1, $2, $3, $4, $5, false, $6,
|
|
233
|
-
`, [refreshRow.user_id, refreshRow.oauth_client_id, hashedToken, refreshRow.name, refreshRow.scopes, expiresAt
|
|
234
|
+
VALUES ($1, $2, $3, $4, $5, false, $6, ${appNow()}, ${appNow()})
|
|
235
|
+
`, [refreshRow.user_id, refreshRow.oauth_client_id, hashedToken, refreshRow.name, refreshRow.scopes, sqlDateTime(expiresAt)]);
|
|
234
236
|
else
|
|
235
237
|
await trx.unsafe(`
|
|
236
238
|
INSERT INTO oauth_access_tokens (user_id, oauth_client_id, token, name, scopes, revoked, expires_at, created_at, updated_at)
|
|
237
|
-
VALUES (?, ?, ?, ?, ?, 0, ?, ${
|
|
238
|
-
`, [refreshRow.user_id, refreshRow.oauth_client_id, hashedToken, refreshRow.name, refreshRow.scopes, expiresAt
|
|
239
|
+
VALUES (?, ?, ?, ?, ?, 0, ?, ${appNow()}, ${appNow()})
|
|
240
|
+
`, [refreshRow.user_id, refreshRow.oauth_client_id, hashedToken, refreshRow.name, refreshRow.scopes, sqlDateTime(expiresAt)]);
|
|
239
241
|
const row = (await trx.unsafe(`
|
|
240
242
|
SELECT * FROM oauth_access_tokens WHERE token = ${param(1)} LIMIT 1
|
|
241
243
|
`, [hashedToken]))[0], accessToken = {
|
|
@@ -256,13 +258,13 @@ export async function refreshToken(refreshTokenPlain, options = {}) {
|
|
|
256
258
|
if (isPostgres)
|
|
257
259
|
await trx.unsafe(`
|
|
258
260
|
INSERT INTO oauth_refresh_tokens (access_token_id, token, revoked, expires_at, created_at)
|
|
259
|
-
VALUES ($1, $2, false, $3,
|
|
260
|
-
`, [accessToken.id, newHashedRefreshToken, refreshExpiresAt
|
|
261
|
+
VALUES ($1, $2, false, $3, ${appNow()})
|
|
262
|
+
`, [accessToken.id, newHashedRefreshToken, sqlDateTime(refreshExpiresAt)]);
|
|
261
263
|
else
|
|
262
264
|
await trx.unsafe(`
|
|
263
265
|
INSERT INTO oauth_refresh_tokens (access_token_id, token, revoked, expires_at, created_at)
|
|
264
|
-
VALUES (?, ?, 0, ?, ${
|
|
265
|
-
`, [accessToken.id, newHashedRefreshToken, refreshExpiresAt
|
|
266
|
+
VALUES (?, ?, 0, ?, ${appNow()})
|
|
267
|
+
`, [accessToken.id, newHashedRefreshToken, sqlDateTime(refreshExpiresAt)]);
|
|
266
268
|
return {
|
|
267
269
|
accessToken,
|
|
268
270
|
plainTextToken,
|
|
@@ -277,7 +279,7 @@ export async function validateRefreshToken(refreshTokenPlain) {
|
|
|
277
279
|
SELECT id FROM oauth_refresh_tokens
|
|
278
280
|
WHERE token = ${param(1)}
|
|
279
281
|
AND revoked = ${boolFalse}
|
|
280
|
-
AND (expires_at IS NULL OR expires_at > ${
|
|
282
|
+
AND (expires_at IS NULL OR expires_at > ${appNow()})
|
|
281
283
|
LIMIT 1
|
|
282
284
|
`, [hashedRefreshToken])).length > 0;
|
|
283
285
|
}
|
|
@@ -301,7 +303,7 @@ export async function revokeAllRefreshTokens(userId) {
|
|
|
301
303
|
export async function deleteExpiredRefreshTokens() {
|
|
302
304
|
const result = await db.unsafe(`
|
|
303
305
|
DELETE FROM oauth_refresh_tokens
|
|
304
|
-
WHERE expires_at < ${
|
|
306
|
+
WHERE expires_at < ${appNow()}
|
|
305
307
|
`);
|
|
306
308
|
return result?.changes || result?.rowCount || 0;
|
|
307
309
|
}
|
|
@@ -311,7 +313,7 @@ export async function deleteRevokedRefreshTokens(daysOld = 7) {
|
|
|
311
313
|
const result = await db.unsafe(`
|
|
312
314
|
DELETE FROM oauth_refresh_tokens
|
|
313
315
|
WHERE revoked = ${boolTrue} AND created_at < ${param(1)}
|
|
314
|
-
`, [cutoffDate
|
|
316
|
+
`, [sqlDateTime(cutoffDate)]);
|
|
315
317
|
return result?.changes || result?.rowCount || 0;
|
|
316
318
|
}
|
|
317
319
|
export async function revokeToken(plainTextToken) {
|
|
@@ -325,7 +327,7 @@ export async function revokeToken(plainTextToken) {
|
|
|
325
327
|
`, [hashedToken]);
|
|
326
328
|
await db.unsafe(`
|
|
327
329
|
UPDATE oauth_access_tokens
|
|
328
|
-
SET revoked = ${boolTrue}, updated_at = ${
|
|
330
|
+
SET revoked = ${boolTrue}, updated_at = ${appNow()}
|
|
329
331
|
WHERE token = ${param(1)}
|
|
330
332
|
`, [hashedToken]);
|
|
331
333
|
}
|
|
@@ -337,7 +339,7 @@ export async function revokeTokenById(tokenId) {
|
|
|
337
339
|
`, [tokenId]);
|
|
338
340
|
await db.unsafe(`
|
|
339
341
|
UPDATE oauth_access_tokens
|
|
340
|
-
SET revoked = ${boolTrue}, updated_at = ${
|
|
342
|
+
SET revoked = ${boolTrue}, updated_at = ${appNow()}
|
|
341
343
|
WHERE id = ${param(1)}
|
|
342
344
|
`, [tokenId]);
|
|
343
345
|
}
|
|
@@ -345,7 +347,7 @@ export async function revokeAllTokens(userId) {
|
|
|
345
347
|
await revokeAllRefreshTokens(userId);
|
|
346
348
|
await db.unsafe(`
|
|
347
349
|
UPDATE oauth_access_tokens
|
|
348
|
-
SET revoked = ${boolTrue}, updated_at = ${
|
|
350
|
+
SET revoked = ${boolTrue}, updated_at = ${appNow()}
|
|
349
351
|
WHERE user_id = ${param(1)}
|
|
350
352
|
`, [userId]);
|
|
351
353
|
}
|
|
@@ -363,7 +365,7 @@ export async function revokeOtherTokens(userId) {
|
|
|
363
365
|
`, [userId, current.id]);
|
|
364
366
|
await db.unsafe(`
|
|
365
367
|
UPDATE oauth_access_tokens
|
|
366
|
-
SET revoked = true, updated_at =
|
|
368
|
+
SET revoked = true, updated_at = ${appNow()}
|
|
367
369
|
WHERE user_id = $1 AND id != $2
|
|
368
370
|
`, [userId, current.id]);
|
|
369
371
|
} else {
|
|
@@ -376,7 +378,7 @@ export async function revokeOtherTokens(userId) {
|
|
|
376
378
|
`, [userId, current.id]);
|
|
377
379
|
await db.unsafe(`
|
|
378
380
|
UPDATE oauth_access_tokens
|
|
379
|
-
SET revoked = 1, updated_at = ${
|
|
381
|
+
SET revoked = 1, updated_at = ${appNow()}
|
|
380
382
|
WHERE user_id = ? AND id != ?
|
|
381
383
|
`, [userId, current.id]);
|
|
382
384
|
}
|
|
@@ -385,12 +387,12 @@ export async function deleteExpiredTokens() {
|
|
|
385
387
|
await db.unsafe(`
|
|
386
388
|
DELETE FROM oauth_refresh_tokens
|
|
387
389
|
WHERE access_token_id IN (
|
|
388
|
-
SELECT id FROM oauth_access_tokens WHERE expires_at < ${
|
|
390
|
+
SELECT id FROM oauth_access_tokens WHERE expires_at < ${appNow()}
|
|
389
391
|
)
|
|
390
392
|
`);
|
|
391
393
|
const result = await db.unsafe(`
|
|
392
394
|
DELETE FROM oauth_access_tokens
|
|
393
|
-
WHERE expires_at < ${
|
|
395
|
+
WHERE expires_at < ${appNow()}
|
|
394
396
|
`);
|
|
395
397
|
return result?.changes || result?.rowCount || 0;
|
|
396
398
|
}
|
|
@@ -402,11 +404,11 @@ export async function deleteRevokedTokens(daysOld = 7) {
|
|
|
402
404
|
WHERE access_token_id IN (
|
|
403
405
|
SELECT id FROM oauth_access_tokens WHERE revoked = ${boolTrue} AND updated_at < ${param(1)}
|
|
404
406
|
)
|
|
405
|
-
`, [cutoffDate
|
|
407
|
+
`, [sqlDateTime(cutoffDate)]);
|
|
406
408
|
const result = await db.unsafe(`
|
|
407
409
|
DELETE FROM oauth_access_tokens
|
|
408
410
|
WHERE revoked = ${boolTrue} AND updated_at < ${param(1)}
|
|
409
|
-
`, [cutoffDate
|
|
411
|
+
`, [sqlDateTime(cutoffDate)]);
|
|
410
412
|
return result?.changes || result?.rowCount || 0;
|
|
411
413
|
}
|
|
412
414
|
export async function clients(userId) {
|
|
@@ -427,7 +429,7 @@ export async function createClient(options) {
|
|
|
427
429
|
if (isPostgres)
|
|
428
430
|
await db.unsafe(`
|
|
429
431
|
INSERT INTO oauth_clients (name, secret, provider, redirect, personal_access_client, password_client, revoked, created_at)
|
|
430
|
-
VALUES ($1, $2, 'local', $3, $4, $5, false,
|
|
432
|
+
VALUES ($1, $2, 'local', $3, $4, $5, false, ${appNow()})
|
|
431
433
|
`, [
|
|
432
434
|
options.name,
|
|
433
435
|
secret,
|
|
@@ -438,7 +440,7 @@ export async function createClient(options) {
|
|
|
438
440
|
else
|
|
439
441
|
await db.unsafe(`
|
|
440
442
|
INSERT INTO oauth_clients (name, secret, provider, redirect, personal_access_client, password_client, revoked, created_at)
|
|
441
|
-
VALUES (?, ?, 'local', ?, ?, ?, 0, ${
|
|
443
|
+
VALUES (?, ?, 'local', ?, ?, ?, 0, ${appNow()})
|
|
442
444
|
`, [
|
|
443
445
|
options.name,
|
|
444
446
|
secret,
|
|
@@ -457,7 +459,7 @@ export async function createClient(options) {
|
|
|
457
459
|
export async function revokeClient(clientId) {
|
|
458
460
|
await db.unsafe(`
|
|
459
461
|
UPDATE oauth_clients
|
|
460
|
-
SET revoked = ${boolTrue}, updated_at = ${
|
|
462
|
+
SET revoked = ${boolTrue}, updated_at = ${appNow()}
|
|
461
463
|
WHERE id = ${param(1)}
|
|
462
464
|
`, [clientId]);
|
|
463
465
|
}
|
package/dist/two-factor.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { db } from "@stacksjs/database";
|
|
2
|
+
import { db, sqlDateTime, parseSqlDateTime } from "@stacksjs/database";
|
|
3
3
|
import { generateTwoFactorSecret, generateTwoFactorUri, verifyTwoFactorCode } from "./authenticator";
|
|
4
4
|
import { RateLimiter } from "./rate-limiter";
|
|
5
5
|
const DEFAULT_CHALLENGE_TTL_SECONDS = 300, TWO_FACTOR_RATE_LIMIT_PREFIX = "2fa:", TWO_FACTOR_STEP_SECONDS = 30;
|
|
@@ -35,7 +35,7 @@ export function generateTwoFactorSetup(email, serviceName) {
|
|
|
35
35
|
}
|
|
36
36
|
const PENDING_SECRET_TTL_SECONDS = 600;
|
|
37
37
|
export async function stashPendingTwoFactorSecret(userId, secret, ttlSeconds = PENDING_SECRET_TTL_SECONDS) {
|
|
38
|
-
const expiresAt = new Date(Date.now() + ttlSeconds * 1000)
|
|
38
|
+
const expiresAt = sqlDateTime(new Date(Date.now() + ttlSeconds * 1000));
|
|
39
39
|
await db.deleteFrom("two_factor_pending_secrets").where("user_id", "=", userId).execute();
|
|
40
40
|
await db.insertInto("two_factor_pending_secrets").values({
|
|
41
41
|
user_id: userId,
|
|
@@ -48,7 +48,7 @@ export async function consumePendingTwoFactorSecret(userId) {
|
|
|
48
48
|
if (!row)
|
|
49
49
|
return null;
|
|
50
50
|
await db.deleteFrom("two_factor_pending_secrets").where("user_id", "=", userId).execute();
|
|
51
|
-
const expiresAt =
|
|
51
|
+
const expiresAt = parseSqlDateTime(row.expires_at)?.getTime() ?? 0;
|
|
52
52
|
if (Date.now() > expiresAt)
|
|
53
53
|
return null;
|
|
54
54
|
return String(row.secret);
|
|
@@ -80,7 +80,7 @@ export async function verifyTwoFactorLoginCode(userId, code) {
|
|
|
80
80
|
return !0;
|
|
81
81
|
}
|
|
82
82
|
export async function createTwoFactorChallenge(userId, ttlSeconds = DEFAULT_CHALLENGE_TTL_SECONDS) {
|
|
83
|
-
const id = randomBytes(32).toString("hex"), expiresAt = new Date(Date.now() + ttlSeconds * 1000)
|
|
83
|
+
const id = randomBytes(32).toString("hex"), expiresAt = sqlDateTime(new Date(Date.now() + ttlSeconds * 1000));
|
|
84
84
|
await db.deleteFrom("two_factor_challenges").where("user_id", "=", userId).execute();
|
|
85
85
|
await db.insertInto("two_factor_challenges").values({
|
|
86
86
|
id,
|
|
@@ -94,7 +94,7 @@ export async function consumeTwoFactorChallenge(challengeToken) {
|
|
|
94
94
|
if (!row)
|
|
95
95
|
return null;
|
|
96
96
|
await db.deleteFrom("two_factor_challenges").where("id", "=", challengeToken).execute();
|
|
97
|
-
const expiresAt =
|
|
97
|
+
const expiresAt = parseSqlDateTime(row.expires_at)?.getTime() ?? 0;
|
|
98
98
|
if (Date.now() > expiresAt)
|
|
99
99
|
return null;
|
|
100
100
|
return Number(row.user_id);
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/auth",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.70.
|
|
5
|
+
"version": "0.70.257",
|
|
6
6
|
"description": "A more simplistic way to authenticate.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"better-dx": "^0.2.17",
|
|
57
|
-
"@stacksjs/error-handling": "0.70.
|
|
58
|
-
"@stacksjs/router": "0.70.
|
|
57
|
+
"@stacksjs/error-handling": "0.70.257",
|
|
58
|
+
"@stacksjs/router": "0.70.257"
|
|
59
59
|
}
|
|
60
60
|
}
|