@absolutejs/auth 0.68.2 → 0.69.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 +69 -0
- package/dist/authContext.d.ts +9 -4
- package/dist/cli/migrate.js +37 -8
- package/dist/cli/migrate.js.map +4 -4
- package/dist/client/createAuthClient.d.ts +28 -2
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +542 -8
- package/dist/client/index.js.map +5 -4
- package/dist/client/mobile.d.ts +84 -0
- package/dist/client/mobile.js +572 -0
- package/dist/client/mobile.js.map +10 -0
- package/dist/htmx/configuredRoutes.d.ts +16 -28
- package/dist/htmx/routes.d.ts +16 -28
- package/dist/index.d.ts +221 -34
- package/dist/index.js +1855 -1401
- package/dist/index.js.map +16 -12
- package/dist/oidc/config.d.ts +17 -2
- package/dist/oidc/inMemoryStores.d.ts +2 -1
- package/dist/oidc/index.d.ts +4 -2
- package/dist/oidc/index.js +589 -6
- package/dist/oidc/index.js.map +8 -4
- package/dist/oidc/nativeClients.d.ts +11 -0
- package/dist/oidc/postgresStores.d.ts +145 -1
- package/dist/oidc/socketTicketRoutes.d.ts +7 -0
- package/dist/oidc/socketTickets.d.ts +21 -0
- package/dist/oidc/types.d.ts +20 -0
- package/dist/principal.d.ts +39 -0
- package/dist/routes/protectRoute.d.ts +7 -3
- package/dist/routes/requireAuth.d.ts +4 -1
- package/dist/server.js +1843 -1400
- package/dist/server.js.map +16 -12
- package/package.json +10 -2
package/dist/oidc/index.js
CHANGED
|
@@ -46,6 +46,141 @@ var __export = (target, all) => {
|
|
|
46
46
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
47
47
|
var __require = import.meta.require;
|
|
48
48
|
|
|
49
|
+
// src/constants.ts
|
|
50
|
+
var SECONDS_IN_A_MINUTE = 60, MILLISECONDS_IN_A_SECOND = 1000, MILLISECONDS_IN_A_MINUTE, MINUTES_IN_AN_HOUR = 60, HOURS_IN_A_DAY = 24, MILLISECONDS_IN_A_DAY, MILLISECONDS_IN_AN_HOUR, COOKIE_MINUTES = 30, COOKIE_DURATION, DEFAULT_MAX_SESSIONS = 1e4;
|
|
51
|
+
var init_constants = __esm(() => {
|
|
52
|
+
MILLISECONDS_IN_A_MINUTE = MILLISECONDS_IN_A_SECOND * SECONDS_IN_A_MINUTE;
|
|
53
|
+
MILLISECONDS_IN_A_DAY = MILLISECONDS_IN_A_SECOND * SECONDS_IN_A_MINUTE * MINUTES_IN_AN_HOUR * HOURS_IN_A_DAY;
|
|
54
|
+
MILLISECONDS_IN_AN_HOUR = MILLISECONDS_IN_A_MINUTE * MINUTES_IN_AN_HOUR;
|
|
55
|
+
COOKIE_DURATION = SECONDS_IN_A_MINUTE * COOKIE_MINUTES;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// src/crypto.ts
|
|
59
|
+
var exports_crypto = {};
|
|
60
|
+
__export(exports_crypto, {
|
|
61
|
+
verifyTotp: () => verifyTotp,
|
|
62
|
+
verifyPassword: () => verifyPassword,
|
|
63
|
+
hashToken: () => hashToken,
|
|
64
|
+
hashPassword: () => hashPassword,
|
|
65
|
+
generateTotpSecret: () => generateTotpSecret,
|
|
66
|
+
generateTotp: () => generateTotp,
|
|
67
|
+
generateSecureToken: () => generateSecureToken,
|
|
68
|
+
generateEncryptionKey: () => generateEncryptionKey,
|
|
69
|
+
encryptSecret: () => encryptSecret,
|
|
70
|
+
decryptSecret: () => decryptSecret,
|
|
71
|
+
createTotpKeyUri: () => createTotpKeyUri,
|
|
72
|
+
constantTimeEqual: () => constantTimeEqual,
|
|
73
|
+
base32Encode: () => base32Encode,
|
|
74
|
+
base32Decode: () => base32Decode
|
|
75
|
+
});
|
|
76
|
+
var DEFAULT_TOKEN_BYTES = 32, AES_KEY_BYTES = 32, AES_IV_BYTES = 12, HOTP_COUNTER_BYTES = 8, TOTP_SECRET_BYTES = 20, TOTP_DIGITS = 6, TOTP_PERIOD_SECONDS = 30, DEFAULT_TOTP_WINDOW = 1, DECIMAL_RADIX = 10, LAST_NIBBLE_MASK = 15, SIGN_BIT_MASK = 2147483647, BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", BASE32_GROUP_BITS = 5, BASE32_MASK = 31, BYTE_BITS = 8, textEncoder, textDecoder3, base64UrlEncode = (bytes) => Buffer.from(bytes).toString("base64url"), base64UrlDecode = (encoded) => new Uint8Array(Buffer.from(encoded, "base64url")), sha256 = async (input) => {
|
|
77
|
+
const digest = await crypto.subtle.digest("SHA-256", textEncoder.encode(input));
|
|
78
|
+
return new Uint8Array(digest);
|
|
79
|
+
}, hmacSha1 = async (key, message) => {
|
|
80
|
+
const cryptoKey = await crypto.subtle.importKey("raw", Uint8Array.from(key), { hash: "SHA-1", name: "HMAC" }, false, ["sign"]);
|
|
81
|
+
const signature = await crypto.subtle.sign("HMAC", cryptoKey, Uint8Array.from(message));
|
|
82
|
+
return new Uint8Array(signature);
|
|
83
|
+
}, counterToBytes = (counter) => {
|
|
84
|
+
const bytes = new Uint8Array(HOTP_COUNTER_BYTES);
|
|
85
|
+
new DataView(bytes.buffer).setBigUint64(0, BigInt(counter), false);
|
|
86
|
+
return bytes;
|
|
87
|
+
}, generateHotp = async (secret, counter, digits = TOTP_DIGITS) => {
|
|
88
|
+
const hmac = await hmacSha1(secret, counterToBytes(counter));
|
|
89
|
+
const view = new DataView(hmac.buffer, hmac.byteOffset, hmac.byteLength);
|
|
90
|
+
const offset = view.getUint8(hmac.byteLength - 1) & LAST_NIBBLE_MASK;
|
|
91
|
+
const truncated = view.getUint32(offset, false) & SIGN_BIT_MASK;
|
|
92
|
+
const otp = truncated % DECIMAL_RADIX ** digits;
|
|
93
|
+
return otp.toString().padStart(digits, "0");
|
|
94
|
+
}, importAesKey = (keyMaterial) => crypto.subtle.importKey("raw", base64UrlDecode(keyMaterial), { name: "AES-GCM" }, false, ["decrypt", "encrypt"]), base32Decode = (encoded) => {
|
|
95
|
+
const normalized = encoded.toUpperCase().replace(/[^A-Z2-7]/gu, "");
|
|
96
|
+
const bits = [...normalized].map((char4) => BASE32_ALPHABET.indexOf(char4).toString(2).padStart(BASE32_GROUP_BITS, "0")).join("");
|
|
97
|
+
const byteChunks = bits.match(/.{8}/gu) ?? [];
|
|
98
|
+
return new Uint8Array(byteChunks.map((chunk) => parseInt(chunk, 2)));
|
|
99
|
+
}, base32Encode = (bytes) => {
|
|
100
|
+
const bits = Array.from(bytes, (byte) => byte.toString(2).padStart(BYTE_BITS, "0")).join("");
|
|
101
|
+
const groups = bits.match(/.{1,5}/gu) ?? [];
|
|
102
|
+
return groups.map((group) => BASE32_ALPHABET[parseInt(group.padEnd(BASE32_GROUP_BITS, "0"), 2) & BASE32_MASK] ?? "").join("");
|
|
103
|
+
}, constantTimeEqual = async (left, right) => {
|
|
104
|
+
const [leftDigest, rightDigest] = await Promise.all([
|
|
105
|
+
sha256(left),
|
|
106
|
+
sha256(right)
|
|
107
|
+
]);
|
|
108
|
+
const leftView = new DataView(leftDigest.buffer, leftDigest.byteOffset, leftDigest.byteLength);
|
|
109
|
+
const rightView = new DataView(rightDigest.buffer, rightDigest.byteOffset, rightDigest.byteLength);
|
|
110
|
+
let mismatch = 0;
|
|
111
|
+
for (let index2 = 0;index2 < leftDigest.byteLength; index2 += 1) {
|
|
112
|
+
mismatch |= leftView.getUint8(index2) ^ rightView.getUint8(index2);
|
|
113
|
+
}
|
|
114
|
+
return mismatch === 0;
|
|
115
|
+
}, createTotpKeyUri = ({
|
|
116
|
+
accountName,
|
|
117
|
+
digits = TOTP_DIGITS,
|
|
118
|
+
issuer,
|
|
119
|
+
period = TOTP_PERIOD_SECONDS,
|
|
120
|
+
secret
|
|
121
|
+
}) => {
|
|
122
|
+
const params = new URLSearchParams({
|
|
123
|
+
algorithm: "SHA1",
|
|
124
|
+
digits: `${digits}`,
|
|
125
|
+
issuer,
|
|
126
|
+
period: `${period}`,
|
|
127
|
+
secret
|
|
128
|
+
});
|
|
129
|
+
const label = encodeURIComponent(`${issuer}:${accountName}`);
|
|
130
|
+
return `otpauth://totp/${label}?${params.toString()}`;
|
|
131
|
+
}, decryptSecret = async (ciphertext, keyMaterial) => {
|
|
132
|
+
const key = await importAesKey(keyMaterial);
|
|
133
|
+
const combined = base64UrlDecode(ciphertext);
|
|
134
|
+
const nonce = combined.subarray(0, AES_IV_BYTES);
|
|
135
|
+
const data = combined.subarray(AES_IV_BYTES);
|
|
136
|
+
const plaintext = await crypto.subtle.decrypt({ iv: nonce, name: "AES-GCM" }, key, data);
|
|
137
|
+
return textDecoder3.decode(plaintext);
|
|
138
|
+
}, encryptSecret = async (plaintext, keyMaterial) => {
|
|
139
|
+
const key = await importAesKey(keyMaterial);
|
|
140
|
+
const nonce = new Uint8Array(AES_IV_BYTES);
|
|
141
|
+
crypto.getRandomValues(nonce);
|
|
142
|
+
const ciphertext = await crypto.subtle.encrypt({ iv: nonce, name: "AES-GCM" }, key, textEncoder.encode(plaintext));
|
|
143
|
+
const combined = new Uint8Array(nonce.byteLength + ciphertext.byteLength);
|
|
144
|
+
combined.set(nonce, 0);
|
|
145
|
+
combined.set(new Uint8Array(ciphertext), nonce.byteLength);
|
|
146
|
+
return base64UrlEncode(combined);
|
|
147
|
+
}, generateEncryptionKey = () => generateSecureToken(AES_KEY_BYTES), generateSecureToken = (byteLength = DEFAULT_TOKEN_BYTES) => {
|
|
148
|
+
const bytes = new Uint8Array(byteLength);
|
|
149
|
+
crypto.getRandomValues(bytes);
|
|
150
|
+
return base64UrlEncode(bytes);
|
|
151
|
+
}, generateTotp = async ({
|
|
152
|
+
digits = TOTP_DIGITS,
|
|
153
|
+
now = Date.now(),
|
|
154
|
+
period = TOTP_PERIOD_SECONDS,
|
|
155
|
+
secret
|
|
156
|
+
}) => {
|
|
157
|
+
const counter = Math.floor(now / MILLISECONDS_IN_A_SECOND / period);
|
|
158
|
+
return generateHotp(base32Decode(secret), counter, digits);
|
|
159
|
+
}, generateTotpSecret = (byteLength = TOTP_SECRET_BYTES) => {
|
|
160
|
+
const bytes = new Uint8Array(byteLength);
|
|
161
|
+
crypto.getRandomValues(bytes);
|
|
162
|
+
return base32Encode(bytes);
|
|
163
|
+
}, hashPassword = (password) => Bun.password.hash(password, { algorithm: "argon2id" }), hashToken = async (token) => base64UrlEncode(await sha256(token)), verifyPassword = (password, hash) => Bun.password.verify(password, hash), verifyTotp = async ({
|
|
164
|
+
digits = TOTP_DIGITS,
|
|
165
|
+
now = Date.now(),
|
|
166
|
+
period = TOTP_PERIOD_SECONDS,
|
|
167
|
+
secret,
|
|
168
|
+
token,
|
|
169
|
+
window = DEFAULT_TOTP_WINDOW
|
|
170
|
+
}) => {
|
|
171
|
+
const secretBytes = base32Decode(secret);
|
|
172
|
+
const counter = Math.floor(now / MILLISECONDS_IN_A_SECOND / period);
|
|
173
|
+
const drifts = Array.from({ length: window * 2 + 1 }, (_, offset) => counter - window + offset);
|
|
174
|
+
const candidates = await Promise.all(drifts.map((value) => generateHotp(secretBytes, value, digits)));
|
|
175
|
+
const matches = await Promise.all(candidates.map((candidate) => constantTimeEqual(candidate, token)));
|
|
176
|
+
return matches.includes(true);
|
|
177
|
+
};
|
|
178
|
+
var init_crypto = __esm(() => {
|
|
179
|
+
init_constants();
|
|
180
|
+
textEncoder = new TextEncoder;
|
|
181
|
+
textDecoder3 = new TextDecoder;
|
|
182
|
+
});
|
|
183
|
+
|
|
49
184
|
// src/oidc/keys.ts
|
|
50
185
|
var ENCODER = new TextEncoder;
|
|
51
186
|
var ES256 = { hash: "SHA-256", name: "ECDSA" };
|
|
@@ -11835,13 +11970,24 @@ var oauthRefreshTokensTable = pgTable("auth_oauth_refresh_tokens", {
|
|
|
11835
11970
|
audience: varchar("audience", { length: URL_LENGTH }),
|
|
11836
11971
|
claims_json: jsonb("claims_json").$type(),
|
|
11837
11972
|
client_id: varchar("client_id", { length: ID_LENGTH }).notNull(),
|
|
11973
|
+
consumed_token_hashes: text("consumed_token_hashes").array().notNull().default([]),
|
|
11838
11974
|
created_at_ms: bigint("created_at_ms", { mode: "number" }).notNull(),
|
|
11839
11975
|
dpop_jkt: varchar("dpop_jkt", { length: ID_LENGTH }),
|
|
11840
11976
|
expires_at_ms: bigint("expires_at_ms", { mode: "number" }).notNull(),
|
|
11977
|
+
family_id: varchar("family_id", { length: ID_LENGTH }).notNull(),
|
|
11978
|
+
revoked_at_ms: bigint("revoked_at_ms", { mode: "number" }),
|
|
11841
11979
|
scopes: text("scopes").array().notNull(),
|
|
11842
11980
|
token_hash: varchar("token_hash", { length: ID_LENGTH }).primaryKey(),
|
|
11843
11981
|
user_id: varchar("user_id", { length: ID_LENGTH }).notNull()
|
|
11844
11982
|
});
|
|
11983
|
+
var oauthSocketTicketsTable = pgTable("auth_oauth_socket_tickets", {
|
|
11984
|
+
audience: varchar("audience", { length: URL_LENGTH }).notNull(),
|
|
11985
|
+
client_id: varchar("client_id", { length: ID_LENGTH }).notNull(),
|
|
11986
|
+
expires_at_ms: bigint("expires_at_ms", { mode: "number" }).notNull(),
|
|
11987
|
+
scopes: text("scopes").array().notNull(),
|
|
11988
|
+
subject: varchar("subject", { length: ID_LENGTH }).notNull(),
|
|
11989
|
+
ticket_hash: varchar("ticket_hash", { length: ID_LENGTH }).primaryKey()
|
|
11990
|
+
});
|
|
11845
11991
|
var toClient = (row) => ({
|
|
11846
11992
|
backchannelLogoutUri: row.backchannel_logout_uri ?? undefined,
|
|
11847
11993
|
clientId: row.client_id,
|
|
@@ -11915,6 +12061,7 @@ var toRefresh = (row) => ({
|
|
|
11915
12061
|
createdAt: row.created_at_ms,
|
|
11916
12062
|
dpopJkt: row.dpop_jkt ?? undefined,
|
|
11917
12063
|
expiresAt: row.expires_at_ms,
|
|
12064
|
+
familyId: row.family_id,
|
|
11918
12065
|
scopes: row.scopes,
|
|
11919
12066
|
tokenHash: row.token_hash,
|
|
11920
12067
|
userId: row.user_id
|
|
@@ -11924,9 +12071,12 @@ var toRefreshValues = (token) => ({
|
|
|
11924
12071
|
audience: token.audience ?? null,
|
|
11925
12072
|
claims_json: token.claims ?? null,
|
|
11926
12073
|
client_id: token.clientId,
|
|
12074
|
+
consumed_token_hashes: [],
|
|
11927
12075
|
created_at_ms: token.createdAt,
|
|
11928
12076
|
dpop_jkt: token.dpopJkt ?? null,
|
|
11929
12077
|
expires_at_ms: token.expiresAt,
|
|
12078
|
+
family_id: token.familyId,
|
|
12079
|
+
revoked_at_ms: null,
|
|
11930
12080
|
scopes: token.scopes,
|
|
11931
12081
|
token_hash: token.tokenHash,
|
|
11932
12082
|
user_id: token.userId
|
|
@@ -12105,7 +12255,7 @@ var createPostgresOAuthClientStore = (db) => ({
|
|
|
12105
12255
|
});
|
|
12106
12256
|
var createPostgresOidcRefreshTokenStore = (db) => ({
|
|
12107
12257
|
consumeToken: async (tokenHash) => {
|
|
12108
|
-
const [row] = await db.delete(oauthRefreshTokensTable).where(eq(oauthRefreshTokensTable.token_hash, tokenHash)).returning();
|
|
12258
|
+
const [row] = await db.delete(oauthRefreshTokensTable).where(or(eq(oauthRefreshTokensTable.token_hash, tokenHash), sql`${tokenHash} = ANY(${oauthRefreshTokensTable.consumed_token_hashes})`)).returning();
|
|
12109
12259
|
return row ? toRefresh(row) : undefined;
|
|
12110
12260
|
},
|
|
12111
12261
|
deleteForClient: async (clientId) => {
|
|
@@ -12120,20 +12270,46 @@ var createPostgresOidcRefreshTokenStore = (db) => ({
|
|
|
12120
12270
|
return deleted.length;
|
|
12121
12271
|
},
|
|
12122
12272
|
getToken: async (tokenHash) => {
|
|
12123
|
-
const [row] = await db.select().from(oauthRefreshTokensTable).where(eq(oauthRefreshTokensTable.token_hash, tokenHash)).limit(1);
|
|
12273
|
+
const [row] = await db.select().from(oauthRefreshTokensTable).where(and(eq(oauthRefreshTokensTable.token_hash, tokenHash), isNull(oauthRefreshTokensTable.revoked_at_ms))).limit(1);
|
|
12124
12274
|
return row ? toRefresh(row) : undefined;
|
|
12125
12275
|
},
|
|
12126
12276
|
listClientIdsForUser: async (userId) => {
|
|
12127
|
-
const rows = await db.selectDistinct({ client_id: oauthRefreshTokensTable.client_id }).from(oauthRefreshTokensTable).where(and(eq(oauthRefreshTokensTable.user_id, userId), gt(oauthRefreshTokensTable.expires_at_ms, Date.now())));
|
|
12277
|
+
const rows = await db.selectDistinct({ client_id: oauthRefreshTokensTable.client_id }).from(oauthRefreshTokensTable).where(and(eq(oauthRefreshTokensTable.user_id, userId), isNull(oauthRefreshTokensTable.revoked_at_ms), gt(oauthRefreshTokensTable.expires_at_ms, Date.now())));
|
|
12128
12278
|
return rows.map((row) => row.client_id);
|
|
12129
12279
|
},
|
|
12130
12280
|
listConnections: async () => {
|
|
12131
12281
|
const rows = await db.selectDistinct({
|
|
12132
12282
|
clientId: oauthRefreshTokensTable.client_id,
|
|
12133
12283
|
userId: oauthRefreshTokensTable.user_id
|
|
12134
|
-
}).from(oauthRefreshTokensTable).where(gt(oauthRefreshTokensTable.expires_at_ms, Date.now()));
|
|
12284
|
+
}).from(oauthRefreshTokensTable).where(and(gt(oauthRefreshTokensTable.expires_at_ms, Date.now()), isNull(oauthRefreshTokensTable.revoked_at_ms)));
|
|
12135
12285
|
return rows;
|
|
12136
12286
|
},
|
|
12287
|
+
revokeByConsumedToken: async (tokenHash) => {
|
|
12288
|
+
const rows = await db.update(oauthRefreshTokensTable).set({ revoked_at_ms: Date.now() }).where(and(isNull(oauthRefreshTokensTable.revoked_at_ms), sql`${tokenHash} = ANY(${oauthRefreshTokensTable.consumed_token_hashes})`)).returning({ familyId: oauthRefreshTokensTable.family_id });
|
|
12289
|
+
return rows.length > 0;
|
|
12290
|
+
},
|
|
12291
|
+
rotateToken: async (currentTokenHash, replacement) => {
|
|
12292
|
+
const active = and(eq(oauthRefreshTokensTable.token_hash, currentTokenHash), isNull(oauthRefreshTokensTable.revoked_at_ms));
|
|
12293
|
+
const consumed = sql`${currentTokenHash} = ANY(${oauthRefreshTokensTable.consumed_token_hashes})`;
|
|
12294
|
+
const rows = await db.update(oauthRefreshTokensTable).set({
|
|
12295
|
+
acr: sql`CASE WHEN ${active} THEN ${replacement.acr ?? null} ELSE ${oauthRefreshTokensTable.acr} END`,
|
|
12296
|
+
audience: sql`CASE WHEN ${active} THEN ${replacement.audience ?? null} ELSE ${oauthRefreshTokensTable.audience} END`,
|
|
12297
|
+
claims_json: sql`CASE WHEN ${active} THEN ${JSON.stringify(replacement.claims ?? null)}::jsonb ELSE ${oauthRefreshTokensTable.claims_json} END`,
|
|
12298
|
+
client_id: sql`CASE WHEN ${active} THEN ${replacement.clientId} ELSE ${oauthRefreshTokensTable.client_id} END`,
|
|
12299
|
+
consumed_token_hashes: sql`CASE WHEN ${active} THEN array_append(${oauthRefreshTokensTable.consumed_token_hashes}, ${currentTokenHash}) ELSE ${oauthRefreshTokensTable.consumed_token_hashes} END`,
|
|
12300
|
+
created_at_ms: sql`CASE WHEN ${active} THEN ${replacement.createdAt} ELSE ${oauthRefreshTokensTable.created_at_ms} END`,
|
|
12301
|
+
dpop_jkt: sql`CASE WHEN ${active} THEN ${replacement.dpopJkt ?? null} ELSE ${oauthRefreshTokensTable.dpop_jkt} END`,
|
|
12302
|
+
expires_at_ms: sql`CASE WHEN ${active} THEN ${replacement.expiresAt} ELSE ${oauthRefreshTokensTable.expires_at_ms} END`,
|
|
12303
|
+
revoked_at_ms: sql`CASE WHEN ${consumed} THEN ${Date.now()} ELSE ${oauthRefreshTokensTable.revoked_at_ms} END`,
|
|
12304
|
+
scopes: sql`CASE WHEN ${active} THEN ${replacement.scopes} ELSE ${oauthRefreshTokensTable.scopes} END`,
|
|
12305
|
+
token_hash: sql`CASE WHEN ${active} THEN ${replacement.tokenHash} ELSE ${oauthRefreshTokensTable.token_hash} END`,
|
|
12306
|
+
user_id: sql`CASE WHEN ${active} THEN ${replacement.userId} ELSE ${oauthRefreshTokensTable.user_id} END`
|
|
12307
|
+
}).where(or(active, consumed)).returning({
|
|
12308
|
+
revokedAt: oauthRefreshTokensTable.revoked_at_ms,
|
|
12309
|
+
tokenHash: oauthRefreshTokensTable.token_hash
|
|
12310
|
+
});
|
|
12311
|
+
return rows.length === 1 && rows[0]?.revokedAt === null && rows[0]?.tokenHash === replacement.tokenHash;
|
|
12312
|
+
},
|
|
12137
12313
|
saveToken: async (token) => {
|
|
12138
12314
|
await db.insert(oauthRefreshTokensTable).values(toRefreshValues(token));
|
|
12139
12315
|
}
|
|
@@ -12207,6 +12383,408 @@ var createPostgresBackchannelAuthStore = (db) => ({
|
|
|
12207
12383
|
await db.update(oauthBackchannelAuthRequestsTable).set({ status, user_sub: userSub ?? null }).where(eq(oauthBackchannelAuthRequestsTable.auth_req_id, authReqId));
|
|
12208
12384
|
}
|
|
12209
12385
|
});
|
|
12386
|
+
var toSocketTicket = (row) => ({
|
|
12387
|
+
audience: row.audience,
|
|
12388
|
+
clientId: row.client_id,
|
|
12389
|
+
expiresAt: row.expires_at_ms,
|
|
12390
|
+
scopes: row.scopes,
|
|
12391
|
+
subject: row.subject,
|
|
12392
|
+
ticketHash: row.ticket_hash
|
|
12393
|
+
});
|
|
12394
|
+
var createNeonSocketTicketStore = (databaseUrl) => createPostgresSocketTicketStore(createNeonDatabase(databaseUrl));
|
|
12395
|
+
var createPostgresSocketTicketStore = (db) => ({
|
|
12396
|
+
consumeTicket: async (ticketHash, now = Date.now()) => {
|
|
12397
|
+
const [row] = await db.delete(oauthSocketTicketsTable).where(eq(oauthSocketTicketsTable.ticket_hash, ticketHash)).returning();
|
|
12398
|
+
if (!row || row.expires_at_ms <= now)
|
|
12399
|
+
return;
|
|
12400
|
+
return toSocketTicket(row);
|
|
12401
|
+
},
|
|
12402
|
+
saveTicket: async (ticket) => {
|
|
12403
|
+
await db.insert(oauthSocketTicketsTable).values({
|
|
12404
|
+
audience: ticket.audience,
|
|
12405
|
+
client_id: ticket.clientId,
|
|
12406
|
+
expires_at_ms: ticket.expiresAt,
|
|
12407
|
+
scopes: ticket.scopes,
|
|
12408
|
+
subject: ticket.subject,
|
|
12409
|
+
ticket_hash: ticket.ticketHash
|
|
12410
|
+
});
|
|
12411
|
+
}
|
|
12412
|
+
});
|
|
12413
|
+
// src/oidc/inMemoryStores.ts
|
|
12414
|
+
var DEFAULT_LIST_LIMIT2 = 100;
|
|
12415
|
+
var createInMemoryAuthorizationCodeStore = () => {
|
|
12416
|
+
const codes = new Map;
|
|
12417
|
+
return {
|
|
12418
|
+
consumeCode: async (codeHash) => {
|
|
12419
|
+
const record = codes.get(codeHash);
|
|
12420
|
+
codes.delete(codeHash);
|
|
12421
|
+
return record;
|
|
12422
|
+
},
|
|
12423
|
+
deleteForClient: async (clientId) => {
|
|
12424
|
+
let deleted = 0;
|
|
12425
|
+
for (const [hash, code] of codes) {
|
|
12426
|
+
if (code.clientId !== clientId)
|
|
12427
|
+
continue;
|
|
12428
|
+
codes.delete(hash);
|
|
12429
|
+
deleted += 1;
|
|
12430
|
+
}
|
|
12431
|
+
return deleted;
|
|
12432
|
+
},
|
|
12433
|
+
deleteForUserClient: async (userId, clientId) => {
|
|
12434
|
+
let deleted = 0;
|
|
12435
|
+
for (const [hash, code] of codes) {
|
|
12436
|
+
if (code.userId !== userId || code.clientId !== clientId)
|
|
12437
|
+
continue;
|
|
12438
|
+
codes.delete(hash);
|
|
12439
|
+
deleted += 1;
|
|
12440
|
+
}
|
|
12441
|
+
return deleted;
|
|
12442
|
+
},
|
|
12443
|
+
saveCode: async (code) => {
|
|
12444
|
+
codes.set(code.codeHash, { ...code });
|
|
12445
|
+
}
|
|
12446
|
+
};
|
|
12447
|
+
};
|
|
12448
|
+
var createInMemoryBackchannelAuthStore = () => {
|
|
12449
|
+
const byAuthReqId = new Map;
|
|
12450
|
+
return {
|
|
12451
|
+
deleteByAuthReqId: async (authReqId) => {
|
|
12452
|
+
byAuthReqId.delete(authReqId);
|
|
12453
|
+
},
|
|
12454
|
+
findByAuthReqId: async (authReqId) => byAuthReqId.get(authReqId),
|
|
12455
|
+
recordPoll: async (authReqId, polledAt) => {
|
|
12456
|
+
const record = byAuthReqId.get(authReqId);
|
|
12457
|
+
if (!record)
|
|
12458
|
+
return;
|
|
12459
|
+
byAuthReqId.set(authReqId, { ...record, lastPolledAt: polledAt });
|
|
12460
|
+
},
|
|
12461
|
+
saveBackchannelAuth: async (request) => {
|
|
12462
|
+
byAuthReqId.set(request.authReqId, { ...request });
|
|
12463
|
+
},
|
|
12464
|
+
updateStatus: async (authReqId, status, userSub) => {
|
|
12465
|
+
const record = byAuthReqId.get(authReqId);
|
|
12466
|
+
if (!record)
|
|
12467
|
+
return;
|
|
12468
|
+
byAuthReqId.set(authReqId, { ...record, status, userSub });
|
|
12469
|
+
}
|
|
12470
|
+
};
|
|
12471
|
+
};
|
|
12472
|
+
var createInMemoryClientAssertionJtiStore = () => {
|
|
12473
|
+
const seen = new Map;
|
|
12474
|
+
return {
|
|
12475
|
+
recordIfFresh: async (clientId, jti, expiresAt) => {
|
|
12476
|
+
const now = Date.now();
|
|
12477
|
+
for (const [key, expiry] of seen) {
|
|
12478
|
+
if (expiry < now)
|
|
12479
|
+
seen.delete(key);
|
|
12480
|
+
}
|
|
12481
|
+
const composite = `${clientId}|${jti}`;
|
|
12482
|
+
if (seen.has(composite))
|
|
12483
|
+
return false;
|
|
12484
|
+
seen.set(composite, expiresAt);
|
|
12485
|
+
return true;
|
|
12486
|
+
}
|
|
12487
|
+
};
|
|
12488
|
+
};
|
|
12489
|
+
var createInMemoryClientRegistrationTokenStore = () => {
|
|
12490
|
+
const byHash = new Map;
|
|
12491
|
+
return {
|
|
12492
|
+
deleteByClientId: async (clientId) => {
|
|
12493
|
+
for (const [hash, token] of byHash) {
|
|
12494
|
+
if (token.clientId === clientId)
|
|
12495
|
+
byHash.delete(hash);
|
|
12496
|
+
}
|
|
12497
|
+
},
|
|
12498
|
+
deleteForClient: async (clientId) => {
|
|
12499
|
+
let deleted = 0;
|
|
12500
|
+
for (const [hash, token] of byHash) {
|
|
12501
|
+
if (token.clientId !== clientId)
|
|
12502
|
+
continue;
|
|
12503
|
+
byHash.delete(hash);
|
|
12504
|
+
deleted += 1;
|
|
12505
|
+
}
|
|
12506
|
+
return deleted;
|
|
12507
|
+
},
|
|
12508
|
+
findByTokenHash: async (tokenHash) => byHash.get(tokenHash),
|
|
12509
|
+
saveToken: async (token) => {
|
|
12510
|
+
for (const [hash, existing] of byHash) {
|
|
12511
|
+
if (existing.clientId === token.clientId)
|
|
12512
|
+
byHash.delete(hash);
|
|
12513
|
+
}
|
|
12514
|
+
byHash.set(token.tokenHash, { ...token });
|
|
12515
|
+
}
|
|
12516
|
+
};
|
|
12517
|
+
};
|
|
12518
|
+
var createInMemoryDeviceAuthorizationStore = () => {
|
|
12519
|
+
const byDeviceCode = new Map;
|
|
12520
|
+
return {
|
|
12521
|
+
deleteByDeviceCodeHash: async (deviceCodeHash) => {
|
|
12522
|
+
byDeviceCode.delete(deviceCodeHash);
|
|
12523
|
+
},
|
|
12524
|
+
deleteForClient: async (clientId) => {
|
|
12525
|
+
let deleted = 0;
|
|
12526
|
+
for (const [hash, authorization] of byDeviceCode) {
|
|
12527
|
+
if (authorization.clientId !== clientId)
|
|
12528
|
+
continue;
|
|
12529
|
+
byDeviceCode.delete(hash);
|
|
12530
|
+
deleted += 1;
|
|
12531
|
+
}
|
|
12532
|
+
return deleted;
|
|
12533
|
+
},
|
|
12534
|
+
deleteForUserClient: async (userId, clientId) => {
|
|
12535
|
+
let deleted = 0;
|
|
12536
|
+
for (const [hash, authorization] of byDeviceCode) {
|
|
12537
|
+
if (authorization.userSub !== userId || authorization.clientId !== clientId)
|
|
12538
|
+
continue;
|
|
12539
|
+
byDeviceCode.delete(hash);
|
|
12540
|
+
deleted += 1;
|
|
12541
|
+
}
|
|
12542
|
+
return deleted;
|
|
12543
|
+
},
|
|
12544
|
+
findByDeviceCodeHash: async (deviceCodeHash) => byDeviceCode.get(deviceCodeHash),
|
|
12545
|
+
findByUserCode: async (userCode) => {
|
|
12546
|
+
for (const record of byDeviceCode.values()) {
|
|
12547
|
+
if (record.userCode === userCode)
|
|
12548
|
+
return record;
|
|
12549
|
+
}
|
|
12550
|
+
return;
|
|
12551
|
+
},
|
|
12552
|
+
saveDeviceAuthorization: async (deviceAuthorization) => {
|
|
12553
|
+
byDeviceCode.set(deviceAuthorization.deviceCodeHash, {
|
|
12554
|
+
...deviceAuthorization
|
|
12555
|
+
});
|
|
12556
|
+
},
|
|
12557
|
+
updateStatus: async (deviceCodeHash, status, userSub) => {
|
|
12558
|
+
const record = byDeviceCode.get(deviceCodeHash);
|
|
12559
|
+
if (!record)
|
|
12560
|
+
return;
|
|
12561
|
+
byDeviceCode.set(deviceCodeHash, {
|
|
12562
|
+
...record,
|
|
12563
|
+
status,
|
|
12564
|
+
userSub
|
|
12565
|
+
});
|
|
12566
|
+
}
|
|
12567
|
+
};
|
|
12568
|
+
};
|
|
12569
|
+
var createInMemoryInitialAccessTokenStore = (initialHashes = []) => {
|
|
12570
|
+
const remaining = new Set(initialHashes);
|
|
12571
|
+
return {
|
|
12572
|
+
consumeToken: async (tokenHash) => {
|
|
12573
|
+
if (!remaining.has(tokenHash))
|
|
12574
|
+
return false;
|
|
12575
|
+
remaining.delete(tokenHash);
|
|
12576
|
+
return true;
|
|
12577
|
+
}
|
|
12578
|
+
};
|
|
12579
|
+
};
|
|
12580
|
+
var createInMemoryLogoutDeliveryStore = () => {
|
|
12581
|
+
const failures = new Map;
|
|
12582
|
+
return {
|
|
12583
|
+
listFailed: async (limit = DEFAULT_LIST_LIMIT2) => Array.from(failures.values()).sort((left, right) => right.createdAt - left.createdAt).slice(0, limit),
|
|
12584
|
+
recordFailure: async (delivery) => {
|
|
12585
|
+
failures.set(delivery.id, delivery);
|
|
12586
|
+
},
|
|
12587
|
+
removeFailure: async (deliveryId) => {
|
|
12588
|
+
failures.delete(deliveryId);
|
|
12589
|
+
}
|
|
12590
|
+
};
|
|
12591
|
+
};
|
|
12592
|
+
var createInMemoryOAuthClientStore = (clients) => {
|
|
12593
|
+
const registry = new Map(clients.map((client) => [client.clientId, client]));
|
|
12594
|
+
return {
|
|
12595
|
+
deleteClient: async (clientId) => {
|
|
12596
|
+
registry.delete(clientId);
|
|
12597
|
+
},
|
|
12598
|
+
findClient: async (clientId) => registry.get(clientId),
|
|
12599
|
+
saveClient: async (client) => {
|
|
12600
|
+
registry.set(client.clientId, { ...client });
|
|
12601
|
+
},
|
|
12602
|
+
updateClient: async (clientId, client) => {
|
|
12603
|
+
registry.set(clientId, { ...client });
|
|
12604
|
+
}
|
|
12605
|
+
};
|
|
12606
|
+
};
|
|
12607
|
+
var createInMemoryOidcRefreshTokenStore = () => {
|
|
12608
|
+
const families = new Map;
|
|
12609
|
+
const activeForHash = (tokenHash) => {
|
|
12610
|
+
for (const family of families.values()) {
|
|
12611
|
+
if (!family.revoked && family.token.tokenHash === tokenHash)
|
|
12612
|
+
return family;
|
|
12613
|
+
}
|
|
12614
|
+
return;
|
|
12615
|
+
};
|
|
12616
|
+
const familyForConsumedHash = (tokenHash) => {
|
|
12617
|
+
for (const family of families.values())
|
|
12618
|
+
if (family.consumed.has(tokenHash))
|
|
12619
|
+
return family;
|
|
12620
|
+
return;
|
|
12621
|
+
};
|
|
12622
|
+
return {
|
|
12623
|
+
consumeToken: async (tokenHash) => {
|
|
12624
|
+
const family = activeForHash(tokenHash) ?? familyForConsumedHash(tokenHash);
|
|
12625
|
+
if (!family)
|
|
12626
|
+
return;
|
|
12627
|
+
families.delete(family.token.familyId);
|
|
12628
|
+
return family.token;
|
|
12629
|
+
},
|
|
12630
|
+
deleteForClient: async (clientId) => {
|
|
12631
|
+
let deleted = 0;
|
|
12632
|
+
for (const [familyId, family] of families) {
|
|
12633
|
+
if (family.token.clientId !== clientId)
|
|
12634
|
+
continue;
|
|
12635
|
+
families.delete(familyId);
|
|
12636
|
+
deleted += 1;
|
|
12637
|
+
}
|
|
12638
|
+
return deleted;
|
|
12639
|
+
},
|
|
12640
|
+
deleteForUser: async (userId) => {
|
|
12641
|
+
for (const [familyId, family] of families) {
|
|
12642
|
+
if (family.token.userId === userId)
|
|
12643
|
+
families.delete(familyId);
|
|
12644
|
+
}
|
|
12645
|
+
},
|
|
12646
|
+
deleteForUserClient: async (userId, clientId) => {
|
|
12647
|
+
let deleted = 0;
|
|
12648
|
+
for (const [familyId, family] of families) {
|
|
12649
|
+
if (family.token.userId !== userId || family.token.clientId !== clientId)
|
|
12650
|
+
continue;
|
|
12651
|
+
families.delete(familyId);
|
|
12652
|
+
deleted += 1;
|
|
12653
|
+
}
|
|
12654
|
+
return deleted;
|
|
12655
|
+
},
|
|
12656
|
+
getToken: async (tokenHash) => activeForHash(tokenHash)?.token,
|
|
12657
|
+
listClientIdsForUser: async (userId) => {
|
|
12658
|
+
const now = Date.now();
|
|
12659
|
+
const active = Array.from(families.values()).filter((family) => !family.revoked && family.token.userId === userId && family.token.expiresAt > now);
|
|
12660
|
+
return Array.from(new Set(active.map((family) => family.token.clientId)));
|
|
12661
|
+
},
|
|
12662
|
+
listConnections: async () => {
|
|
12663
|
+
const now = Date.now();
|
|
12664
|
+
const connections = new Map;
|
|
12665
|
+
for (const family of families.values()) {
|
|
12666
|
+
const { token } = family;
|
|
12667
|
+
if (family.revoked || token.expiresAt <= now)
|
|
12668
|
+
continue;
|
|
12669
|
+
const connection = {
|
|
12670
|
+
clientId: token.clientId,
|
|
12671
|
+
userId: token.userId
|
|
12672
|
+
};
|
|
12673
|
+
connections.set(`${connection.userId}\x00${connection.clientId}`, connection);
|
|
12674
|
+
}
|
|
12675
|
+
return Array.from(connections.values());
|
|
12676
|
+
},
|
|
12677
|
+
revokeByConsumedToken: async (tokenHash) => {
|
|
12678
|
+
const family = familyForConsumedHash(tokenHash);
|
|
12679
|
+
if (!family)
|
|
12680
|
+
return false;
|
|
12681
|
+
family.revoked = true;
|
|
12682
|
+
return true;
|
|
12683
|
+
},
|
|
12684
|
+
rotateToken: async (currentTokenHash, replacement) => {
|
|
12685
|
+
const family = activeForHash(currentTokenHash);
|
|
12686
|
+
if (family) {
|
|
12687
|
+
family.consumed.add(currentTokenHash);
|
|
12688
|
+
family.token = { ...replacement };
|
|
12689
|
+
return true;
|
|
12690
|
+
}
|
|
12691
|
+
const reused = familyForConsumedHash(currentTokenHash);
|
|
12692
|
+
if (reused)
|
|
12693
|
+
reused.revoked = true;
|
|
12694
|
+
return false;
|
|
12695
|
+
},
|
|
12696
|
+
saveToken: async (token) => {
|
|
12697
|
+
families.set(token.familyId, {
|
|
12698
|
+
consumed: new Set,
|
|
12699
|
+
revoked: false,
|
|
12700
|
+
token: { ...token }
|
|
12701
|
+
});
|
|
12702
|
+
}
|
|
12703
|
+
};
|
|
12704
|
+
};
|
|
12705
|
+
var createInMemoryPushedAuthorizationRequestStore = () => {
|
|
12706
|
+
const requests = new Map;
|
|
12707
|
+
return {
|
|
12708
|
+
consumeRequest: async (requestUriHash) => {
|
|
12709
|
+
const record = requests.get(requestUriHash);
|
|
12710
|
+
if (record === undefined)
|
|
12711
|
+
return;
|
|
12712
|
+
requests.delete(requestUriHash);
|
|
12713
|
+
if (record.expiresAt < Date.now())
|
|
12714
|
+
return;
|
|
12715
|
+
return record;
|
|
12716
|
+
},
|
|
12717
|
+
saveRequest: async (request) => {
|
|
12718
|
+
requests.set(request.requestUriHash, { ...request });
|
|
12719
|
+
}
|
|
12720
|
+
};
|
|
12721
|
+
};
|
|
12722
|
+
var createInMemorySocketTicketStore = () => {
|
|
12723
|
+
const tickets = new Map;
|
|
12724
|
+
return {
|
|
12725
|
+
consumeTicket: async (ticketHash, now = Date.now()) => {
|
|
12726
|
+
const ticket = tickets.get(ticketHash);
|
|
12727
|
+
tickets.delete(ticketHash);
|
|
12728
|
+
if (!ticket || ticket.expiresAt <= now)
|
|
12729
|
+
return;
|
|
12730
|
+
return { ...ticket, scopes: [...ticket.scopes] };
|
|
12731
|
+
},
|
|
12732
|
+
saveTicket: async (ticket) => {
|
|
12733
|
+
tickets.set(ticket.ticketHash, {
|
|
12734
|
+
...ticket,
|
|
12735
|
+
scopes: [...ticket.scopes]
|
|
12736
|
+
});
|
|
12737
|
+
}
|
|
12738
|
+
};
|
|
12739
|
+
};
|
|
12740
|
+
// src/oidc/socketTickets.ts
|
|
12741
|
+
init_crypto();
|
|
12742
|
+
var DEFAULT_SOCKET_TICKET_TTL_MS = 30000;
|
|
12743
|
+
var TICKET_BYTES = 32;
|
|
12744
|
+
var consumeSocketTicket = async ({
|
|
12745
|
+
audience,
|
|
12746
|
+
getUser,
|
|
12747
|
+
now = Date.now(),
|
|
12748
|
+
store,
|
|
12749
|
+
ticket
|
|
12750
|
+
}) => {
|
|
12751
|
+
const record = await store.consumeTicket(await hashToken(ticket), now);
|
|
12752
|
+
if (!record || record.audience !== audience)
|
|
12753
|
+
return;
|
|
12754
|
+
const user = await getUser(record.subject);
|
|
12755
|
+
if (user === null)
|
|
12756
|
+
return;
|
|
12757
|
+
return {
|
|
12758
|
+
audience: record.audience,
|
|
12759
|
+
clientId: record.clientId,
|
|
12760
|
+
kind: "access-token",
|
|
12761
|
+
scopes: [...record.scopes],
|
|
12762
|
+
subject: record.subject,
|
|
12763
|
+
user
|
|
12764
|
+
};
|
|
12765
|
+
};
|
|
12766
|
+
var issueSocketTicket = async ({
|
|
12767
|
+
audience,
|
|
12768
|
+
clientId,
|
|
12769
|
+
now = Date.now(),
|
|
12770
|
+
scopes,
|
|
12771
|
+
store,
|
|
12772
|
+
subject,
|
|
12773
|
+
ttlMs = DEFAULT_SOCKET_TICKET_TTL_MS
|
|
12774
|
+
}) => {
|
|
12775
|
+
if (!Number.isFinite(ttlMs) || ttlMs <= 0)
|
|
12776
|
+
throw new Error("socketTicketTtlMs must be positive");
|
|
12777
|
+
const ticket = `ast_${generateSecureToken(TICKET_BYTES)}`;
|
|
12778
|
+
await store.saveTicket({
|
|
12779
|
+
audience,
|
|
12780
|
+
clientId,
|
|
12781
|
+
expiresAt: now + ttlMs,
|
|
12782
|
+
scopes: [...scopes],
|
|
12783
|
+
subject,
|
|
12784
|
+
ticketHash: await hashToken(ticket)
|
|
12785
|
+
});
|
|
12786
|
+
return { expiresInMs: ttlMs, ticket };
|
|
12787
|
+
};
|
|
12210
12788
|
export {
|
|
12211
12789
|
verifyJwtWithKeys,
|
|
12212
12790
|
verifyJwt,
|
|
@@ -12215,7 +12793,9 @@ export {
|
|
|
12215
12793
|
signJwt,
|
|
12216
12794
|
revokeOAuthClientCredentials,
|
|
12217
12795
|
jwkThumbprint,
|
|
12796
|
+
issueSocketTicket,
|
|
12218
12797
|
generateSigningKey,
|
|
12798
|
+
createPostgresSocketTicketStore,
|
|
12219
12799
|
createPostgresOidcRefreshTokenStore,
|
|
12220
12800
|
createPostgresOAuthClientStore,
|
|
12221
12801
|
createPostgresInitialAccessTokenStore,
|
|
@@ -12223,14 +12803,17 @@ export {
|
|
|
12223
12803
|
createPostgresClientRegistrationTokenStore,
|
|
12224
12804
|
createPostgresClientAssertionJtiStore,
|
|
12225
12805
|
createPostgresAuthorizationCodeStore,
|
|
12806
|
+
createNeonSocketTicketStore,
|
|
12226
12807
|
createNeonOidcRefreshTokenStore,
|
|
12227
12808
|
createNeonOAuthClientStore,
|
|
12228
12809
|
createNeonInitialAccessTokenStore,
|
|
12229
12810
|
createNeonDeviceAuthorizationStore,
|
|
12230
12811
|
createNeonClientRegistrationTokenStore,
|
|
12231
12812
|
createNeonClientAssertionJtiStore,
|
|
12232
|
-
createNeonAuthorizationCodeStore
|
|
12813
|
+
createNeonAuthorizationCodeStore,
|
|
12814
|
+
createInMemorySocketTicketStore,
|
|
12815
|
+
consumeSocketTicket
|
|
12233
12816
|
};
|
|
12234
12817
|
|
|
12235
|
-
//# debugId=
|
|
12818
|
+
//# debugId=B37CCAB8EEBB54A964756E2164756E21
|
|
12236
12819
|
//# sourceMappingURL=index.js.map
|