@puzzle-ui/session 0.0.0-stage → 0.5.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.
Files changed (61) hide show
  1. package/README.md +158 -2
  2. package/dist/LICENSE +21 -0
  3. package/dist/errors/session.errors.cjs +47 -0
  4. package/dist/errors/session.errors.cjs.map +1 -0
  5. package/dist/errors/session.errors.d.cts +19 -0
  6. package/dist/errors/session.errors.d.ts +19 -0
  7. package/dist/errors/session.errors.js +42 -0
  8. package/dist/errors/session.errors.js.map +1 -0
  9. package/dist/index.cjs +32 -0
  10. package/dist/index.d.cts +12 -0
  11. package/dist/index.d.ts +12 -0
  12. package/dist/index.js +9 -0
  13. package/dist/session-data.manager.cjs +141 -0
  14. package/dist/session-data.manager.cjs.map +1 -0
  15. package/dist/session-data.manager.d.cts +26 -0
  16. package/dist/session-data.manager.d.ts +26 -0
  17. package/dist/session-data.manager.js +141 -0
  18. package/dist/session-data.manager.js.map +1 -0
  19. package/dist/session.cookies.cjs +64 -0
  20. package/dist/session.cookies.cjs.map +1 -0
  21. package/dist/session.cookies.d.cts +10 -0
  22. package/dist/session.cookies.d.ts +10 -0
  23. package/dist/session.cookies.js +61 -0
  24. package/dist/session.cookies.js.map +1 -0
  25. package/dist/session.csrf.cjs +42 -0
  26. package/dist/session.csrf.cjs.map +1 -0
  27. package/dist/session.csrf.d.cts +6 -0
  28. package/dist/session.csrf.d.ts +6 -0
  29. package/dist/session.csrf.js +41 -0
  30. package/dist/session.csrf.js.map +1 -0
  31. package/dist/session.manager.cjs +289 -0
  32. package/dist/session.manager.cjs.map +1 -0
  33. package/dist/session.manager.d.cts +25 -0
  34. package/dist/session.manager.d.ts +25 -0
  35. package/dist/session.manager.js +289 -0
  36. package/dist/session.manager.js.map +1 -0
  37. package/dist/session.migration.cjs +163 -0
  38. package/dist/session.migration.cjs.map +1 -0
  39. package/dist/session.migration.d.cts +50 -0
  40. package/dist/session.migration.d.ts +50 -0
  41. package/dist/session.migration.js +163 -0
  42. package/dist/session.migration.js.map +1 -0
  43. package/dist/session.storage.cjs +64 -0
  44. package/dist/session.storage.cjs.map +1 -0
  45. package/dist/session.storage.d.cts +8 -0
  46. package/dist/session.storage.d.ts +8 -0
  47. package/dist/session.storage.js +58 -0
  48. package/dist/session.storage.js.map +1 -0
  49. package/dist/types/session-lock.types.d.cts +4 -0
  50. package/dist/types/session-lock.types.d.ts +4 -0
  51. package/dist/types/session-manager.types.d.cts +37 -0
  52. package/dist/types/session-manager.types.d.ts +37 -0
  53. package/dist/types/session.types.d.cts +51 -0
  54. package/dist/types/session.types.d.ts +51 -0
  55. package/dist/utils/session-crypto.cjs +29 -0
  56. package/dist/utils/session-crypto.cjs.map +1 -0
  57. package/dist/utils/session-crypto.d.cts +3 -0
  58. package/dist/utils/session-crypto.d.ts +3 -0
  59. package/dist/utils/session-crypto.js +27 -0
  60. package/dist/utils/session-crypto.js.map +1 -0
  61. package/package.json +48 -4
@@ -0,0 +1,8 @@
1
+ import { SessionDataRecord, SessionStorageRecord } from "./types/session.types.js";
2
+ import "./types/session-lock.types.js";
3
+ import { StorageAdapter, StorageCollection } from "@puzzle-ui/storage";
4
+ export declare const SESSION_COLLECTION = "sessions";
5
+ export declare const SESSION_DATA_COLLECTION = "session_data";
6
+ export declare const sessionCollection: StorageCollection<SessionStorageRecord>;
7
+ export declare const sessionDataCollection: StorageCollection<SessionDataRecord>;
8
+ export declare const initializeSessionStorage: (storage: StorageAdapter) => Promise<void>;
@@ -0,0 +1,58 @@
1
+ // src/session.storage.ts
2
+ const SESSION_COLLECTION = "sessions";
3
+ const SESSION_DATA_COLLECTION = "session_data";
4
+ const SESSION_USER_LOCK_COLLECTION = "session_user_locks";
5
+ const sessionCollection = {
6
+ name: SESSION_COLLECTION,
7
+ indexes: [
8
+ {
9
+ name: "token_hash_unique",
10
+ fields: ["tokenHash"],
11
+ unique: true
12
+ },
13
+ {
14
+ name: "user_id",
15
+ fields: ["userId"]
16
+ },
17
+ {
18
+ name: "expires_at",
19
+ fields: ["expiresAt"]
20
+ },
21
+ {
22
+ name: "revoked_at",
23
+ fields: ["revokedAt"]
24
+ }
25
+ ]
26
+ };
27
+ const sessionDataCollection = {
28
+ name: SESSION_DATA_COLLECTION,
29
+ indexes: [{
30
+ name: "owner_namespace_key",
31
+ fields: [
32
+ "ownerId",
33
+ "namespace",
34
+ "key"
35
+ ],
36
+ unique: true
37
+ }, {
38
+ name: "expires_at",
39
+ fields: ["expiresAt"]
40
+ }]
41
+ };
42
+ const sessionUserLockCollection = {
43
+ name: SESSION_USER_LOCK_COLLECTION,
44
+ indexes: [{
45
+ name: "user_id_unique",
46
+ fields: ["userId"],
47
+ unique: true
48
+ }]
49
+ };
50
+ const initializeSessionStorage = async (storage) => {
51
+ if (!storage.capabilities.uniqueConstraints || !storage.capabilities.conditionalWrites || !storage.capabilities.atomicBatch) throw new Error("Session storage requires unique constraints, conditional writes and atomic batches.");
52
+ await storage.registerCollection(sessionCollection);
53
+ await storage.registerCollection(sessionDataCollection);
54
+ await storage.registerCollection(sessionUserLockCollection);
55
+ };
56
+ export { SESSION_COLLECTION, SESSION_DATA_COLLECTION, SESSION_USER_LOCK_COLLECTION, initializeSessionStorage, sessionCollection, sessionDataCollection, sessionUserLockCollection };
57
+
58
+ //# sourceMappingURL=session.storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.storage.js","names":[],"sources":["../src/session.storage.ts"],"sourcesContent":["import type { StorageAdapter, StorageCollection } from \"@puzzle-ui/storage\";\n\nimport type {\n SessionDataRecord,\n SessionStorageRecord,\n} from \"./types/session.types\";\n\nimport type { SessionUserLockRecord } from \"./types/session-lock.types\";\n\nexport const SESSION_COLLECTION = \"sessions\";\nexport const SESSION_DATA_COLLECTION = \"session_data\";\nexport const SESSION_USER_LOCK_COLLECTION = \"session_user_locks\";\n\nexport const sessionCollection: StorageCollection<SessionStorageRecord> = {\n name: SESSION_COLLECTION,\n indexes: [\n {\n name: \"token_hash_unique\",\n fields: [\"tokenHash\"],\n unique: true,\n },\n {\n name: \"user_id\",\n fields: [\"userId\"],\n },\n {\n name: \"expires_at\",\n fields: [\"expiresAt\"],\n },\n {\n name: \"revoked_at\",\n fields: [\"revokedAt\"],\n },\n ],\n};\n\nexport const sessionDataCollection: StorageCollection<SessionDataRecord> = {\n name: SESSION_DATA_COLLECTION,\n indexes: [\n {\n name: \"owner_namespace_key\",\n fields: [\"ownerId\", \"namespace\", \"key\"],\n unique: true,\n },\n {\n name: \"expires_at\",\n fields: [\"expiresAt\"],\n },\n ],\n};\n\nexport const sessionUserLockCollection: StorageCollection<SessionUserLockRecord> =\n {\n name: SESSION_USER_LOCK_COLLECTION,\n indexes: [\n {\n name: \"user_id_unique\",\n fields: [\"userId\"],\n unique: true,\n },\n ],\n };\n\nexport const initializeSessionStorage = async (\n storage: StorageAdapter,\n): Promise<void> => {\n if (\n !storage.capabilities.uniqueConstraints ||\n !storage.capabilities.conditionalWrites ||\n !storage.capabilities.atomicBatch\n ) {\n throw new Error(\n \"Session storage requires unique constraints, conditional writes and atomic batches.\",\n );\n }\n\n await storage.registerCollection(sessionCollection);\n await storage.registerCollection(sessionDataCollection);\n await storage.registerCollection(sessionUserLockCollection);\n};\n"],"mappings":";AASA,MAAa,qBAAqB;AAClC,MAAa,0BAA0B;AACvC,MAAa,+BAA+B;AAE5C,MAAa,oBAA6D;CACxE,MAAM;CACN,SAAS;EACP;GACE,MAAM;GACN,QAAQ,CAAC,WAAW;GACpB,QAAQ;EACV;EACA;GACE,MAAM;GACN,QAAQ,CAAC,QAAQ;EACnB;EACA;GACE,MAAM;GACN,QAAQ,CAAC,WAAW;EACtB;EACA;GACE,MAAM;GACN,QAAQ,CAAC,WAAW;EACtB;CACF;AACF;AAEA,MAAa,wBAA8D;CACzE,MAAM;CACN,SAAS,CACP;EACE,MAAM;EACN,QAAQ;GAAC;GAAW;GAAa;EAAK;EACtC,QAAQ;CACV,GACA;EACE,MAAM;EACN,QAAQ,CAAC,WAAW;CACtB,CACF;AACF;AAEA,MAAa,4BACX;CACE,MAAM;CACN,SAAS,CACP;EACE,MAAM;EACN,QAAQ,CAAC,QAAQ;EACjB,QAAQ;CACV,CACF;AACF;AAEF,MAAa,2BAA2B,OACtC,YACkB;CAClB,IACE,CAAC,QAAQ,aAAa,qBACtB,CAAC,QAAQ,aAAa,qBACtB,CAAC,QAAQ,aAAa,aAEtB,MAAM,IAAI,MACR,qFACF;CAGF,MAAM,QAAQ,mBAAmB,iBAAiB;CAClD,MAAM,QAAQ,mBAAmB,qBAAqB;CACtD,MAAM,QAAQ,mBAAmB,yBAAyB;AAC5D"}
@@ -0,0 +1,4 @@
1
+ import { StorageRecord } from "@puzzle-ui/storage";
2
+ export type SessionUserLockRecord = StorageRecord & {
3
+ readonly userId: string;
4
+ };
@@ -0,0 +1,4 @@
1
+ import { StorageRecord } from "@puzzle-ui/storage";
2
+ export type SessionUserLockRecord = StorageRecord & {
3
+ readonly userId: string;
4
+ };
@@ -0,0 +1,37 @@
1
+ import { SessionDevice, SessionKind, SessionRecord } from "./session.types.cjs";
2
+ export type CreateSessionInput = {
3
+ readonly userId: string | null;
4
+ readonly kind: SessionKind;
5
+ readonly expiresAt?: Date;
6
+ readonly metadata?: Readonly<Record<string, unknown>>;
7
+ };
8
+ export type RotateSessionInput = {
9
+ readonly sessionId: string;
10
+ readonly metadata?: Readonly<Record<string, unknown>>;
11
+ };
12
+ export type ValidateSessionOptions = {
13
+ readonly touch?: boolean;
14
+ };
15
+ export type SessionManagerOptions = {
16
+ readonly lifetime: {
17
+ readonly idleTimeoutSeconds: number;
18
+ readonly absoluteLifetimeSeconds: number;
19
+ };
20
+ readonly collectionName?: string;
21
+ readonly maximumSessionsPerUser?: number;
22
+ readonly touchIntervalSeconds?: number;
23
+ readonly clock?: () => Date;
24
+ };
25
+ export type CreatedSession = {
26
+ readonly session: SessionRecord;
27
+ readonly token: string;
28
+ };
29
+ export interface SessionManager {
30
+ create(input: CreateSessionInput): Promise<CreatedSession>;
31
+ validate(token: string, options?: ValidateSessionOptions): Promise<SessionRecord | null>;
32
+ rotate(input: RotateSessionInput): Promise<CreatedSession>;
33
+ revoke(sessionId: string): Promise<void>;
34
+ revokeAll(userId: string): Promise<void>;
35
+ list(userId: string, currentSessionId?: string): Promise<readonly SessionDevice[]>;
36
+ get(sessionId: string): Promise<SessionRecord | null>;
37
+ }
@@ -0,0 +1,37 @@
1
+ import { SessionDevice, SessionKind, SessionRecord } from "./session.types.js";
2
+ export type CreateSessionInput = {
3
+ readonly userId: string | null;
4
+ readonly kind: SessionKind;
5
+ readonly expiresAt?: Date;
6
+ readonly metadata?: Readonly<Record<string, unknown>>;
7
+ };
8
+ export type RotateSessionInput = {
9
+ readonly sessionId: string;
10
+ readonly metadata?: Readonly<Record<string, unknown>>;
11
+ };
12
+ export type ValidateSessionOptions = {
13
+ readonly touch?: boolean;
14
+ };
15
+ export type SessionManagerOptions = {
16
+ readonly lifetime: {
17
+ readonly idleTimeoutSeconds: number;
18
+ readonly absoluteLifetimeSeconds: number;
19
+ };
20
+ readonly collectionName?: string;
21
+ readonly maximumSessionsPerUser?: number;
22
+ readonly touchIntervalSeconds?: number;
23
+ readonly clock?: () => Date;
24
+ };
25
+ export type CreatedSession = {
26
+ readonly session: SessionRecord;
27
+ readonly token: string;
28
+ };
29
+ export interface SessionManager {
30
+ create(input: CreateSessionInput): Promise<CreatedSession>;
31
+ validate(token: string, options?: ValidateSessionOptions): Promise<SessionRecord | null>;
32
+ rotate(input: RotateSessionInput): Promise<CreatedSession>;
33
+ revoke(sessionId: string): Promise<void>;
34
+ revokeAll(userId: string): Promise<void>;
35
+ list(userId: string, currentSessionId?: string): Promise<readonly SessionDevice[]>;
36
+ get(sessionId: string): Promise<SessionRecord | null>;
37
+ }
@@ -0,0 +1,51 @@
1
+ export type SessionKind = "anonymous" | "authenticated";
2
+ export type SessionRecord = {
3
+ readonly id: string;
4
+ readonly tokenHash: string;
5
+ readonly userId: string | null;
6
+ readonly kind: SessionKind;
7
+ readonly createdAt: Date;
8
+ readonly lastSeenAt: Date;
9
+ readonly expiresAt: Date;
10
+ readonly revokedAt: Date | null;
11
+ readonly metadata?: Readonly<Record<string, unknown>>;
12
+ };
13
+ export type SessionLifetime = {
14
+ readonly idleTimeoutSeconds: number;
15
+ readonly absoluteLifetimeSeconds: number;
16
+ };
17
+ export type SessionCookieOptions = {
18
+ readonly name?: string;
19
+ readonly path?: "/";
20
+ readonly httpOnly: true;
21
+ readonly secure: boolean;
22
+ readonly sameSite: "lax" | "strict" | "none";
23
+ readonly maxAge?: number;
24
+ readonly domain?: never;
25
+ };
26
+ export type SessionDevice = {
27
+ readonly sessionId: string;
28
+ readonly kind: SessionKind;
29
+ readonly createdAt: Date;
30
+ readonly lastSeenAt: Date;
31
+ readonly expiresAt: Date;
32
+ readonly current: boolean;
33
+ readonly metadata?: Readonly<Record<string, unknown>>;
34
+ };
35
+ export type SessionStorageRecord = SessionRecord & {
36
+ readonly createdAt: Date;
37
+ readonly updatedAt: Date;
38
+ readonly revision: number;
39
+ };
40
+ export type SessionDataRecord = {
41
+ readonly id: string;
42
+ readonly ownerId: string;
43
+ readonly namespace: string;
44
+ readonly key: string;
45
+ readonly value: unknown;
46
+ readonly expiresAt: Date | null;
47
+ readonly createdAt: Date;
48
+ readonly updatedAt: Date;
49
+ readonly revokedAt?: Date | null;
50
+ readonly revision: number;
51
+ };
@@ -0,0 +1,51 @@
1
+ export type SessionKind = "anonymous" | "authenticated";
2
+ export type SessionRecord = {
3
+ readonly id: string;
4
+ readonly tokenHash: string;
5
+ readonly userId: string | null;
6
+ readonly kind: SessionKind;
7
+ readonly createdAt: Date;
8
+ readonly lastSeenAt: Date;
9
+ readonly expiresAt: Date;
10
+ readonly revokedAt: Date | null;
11
+ readonly metadata?: Readonly<Record<string, unknown>>;
12
+ };
13
+ export type SessionLifetime = {
14
+ readonly idleTimeoutSeconds: number;
15
+ readonly absoluteLifetimeSeconds: number;
16
+ };
17
+ export type SessionCookieOptions = {
18
+ readonly name?: string;
19
+ readonly path?: "/";
20
+ readonly httpOnly: true;
21
+ readonly secure: boolean;
22
+ readonly sameSite: "lax" | "strict" | "none";
23
+ readonly maxAge?: number;
24
+ readonly domain?: never;
25
+ };
26
+ export type SessionDevice = {
27
+ readonly sessionId: string;
28
+ readonly kind: SessionKind;
29
+ readonly createdAt: Date;
30
+ readonly lastSeenAt: Date;
31
+ readonly expiresAt: Date;
32
+ readonly current: boolean;
33
+ readonly metadata?: Readonly<Record<string, unknown>>;
34
+ };
35
+ export type SessionStorageRecord = SessionRecord & {
36
+ readonly createdAt: Date;
37
+ readonly updatedAt: Date;
38
+ readonly revision: number;
39
+ };
40
+ export type SessionDataRecord = {
41
+ readonly id: string;
42
+ readonly ownerId: string;
43
+ readonly namespace: string;
44
+ readonly key: string;
45
+ readonly value: unknown;
46
+ readonly expiresAt: Date | null;
47
+ readonly createdAt: Date;
48
+ readonly updatedAt: Date;
49
+ readonly revokedAt?: Date | null;
50
+ readonly revision: number;
51
+ };
@@ -0,0 +1,29 @@
1
+ const require_session_errors = require("../errors/session.errors.cjs");
2
+ // src/utils/session-crypto.ts
3
+ const TOKEN_BYTES = 32;
4
+ const toBase64Url = (bytes) => {
5
+ let binary = "";
6
+ for (const byte of bytes) binary += String.fromCharCode(byte);
7
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
8
+ };
9
+ const generateSessionToken = (cryptoProvider = globalThis.crypto) => {
10
+ if (!cryptoProvider?.getRandomValues) throw new require_session_errors.SessionSecurityError("A cryptographically secure random generator is required.");
11
+ const bytes = new Uint8Array(TOKEN_BYTES);
12
+ cryptoProvider.getRandomValues(bytes);
13
+ return toBase64Url(bytes);
14
+ };
15
+ const hashSessionToken = async (token, cryptoProvider = globalThis.crypto) => {
16
+ if (!token || token.length < 32) throw new require_session_errors.SessionSecurityError("Invalid session token.");
17
+ if (!cryptoProvider?.subtle) throw new require_session_errors.SessionSecurityError("Web Crypto SubtleCrypto is required.");
18
+ const digest = await cryptoProvider.subtle.digest("SHA-256", new TextEncoder().encode(token));
19
+ return toBase64Url(new Uint8Array(digest));
20
+ };
21
+ const generateSessionId = (cryptoProvider = globalThis.crypto) => {
22
+ if (!cryptoProvider?.randomUUID) throw new require_session_errors.SessionSecurityError("A cryptographically secure UUID generator is required.");
23
+ return cryptoProvider.randomUUID();
24
+ };
25
+ exports.generateSessionId = generateSessionId;
26
+ exports.generateSessionToken = generateSessionToken;
27
+ exports.hashSessionToken = hashSessionToken;
28
+
29
+ //# sourceMappingURL=session-crypto.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-crypto.cjs","names":["SessionSecurityError"],"sources":["../../src/utils/session-crypto.ts"],"sourcesContent":["import { SessionSecurityError } from \"../errors/session.errors\";\n\nconst TOKEN_BYTES = 32;\n\nconst toBase64Url = (bytes: Uint8Array): string => {\n let binary = \"\";\n\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n\n return btoa(binary)\n .replaceAll(\"+\", \"-\")\n .replaceAll(\"/\", \"_\")\n .replace(/=+$/, \"\");\n};\n\nexport const generateSessionToken = (\n cryptoProvider: Crypto = globalThis.crypto,\n): string => {\n if (!cryptoProvider?.getRandomValues) {\n throw new SessionSecurityError(\n \"A cryptographically secure random generator is required.\",\n );\n }\n\n const bytes = new Uint8Array(TOKEN_BYTES);\n\n cryptoProvider.getRandomValues(bytes);\n\n return toBase64Url(bytes);\n};\n\nexport const hashSessionToken = async (\n token: string,\n cryptoProvider: Crypto = globalThis.crypto,\n): Promise<string> => {\n if (!token || token.length < 32) {\n throw new SessionSecurityError(\"Invalid session token.\");\n }\n\n if (!cryptoProvider?.subtle) {\n throw new SessionSecurityError(\"Web Crypto SubtleCrypto is required.\");\n }\n\n const digest = await cryptoProvider.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(token),\n );\n\n return toBase64Url(new Uint8Array(digest));\n};\n\nexport const generateSessionId = (\n cryptoProvider: Crypto = globalThis.crypto,\n): string => {\n if (!cryptoProvider?.randomUUID) {\n throw new SessionSecurityError(\n \"A cryptographically secure UUID generator is required.\",\n );\n }\n\n return cryptoProvider.randomUUID();\n};\n"],"mappings":";;AAEA,MAAM,cAAc;AAEpB,MAAM,eAAe,UAA8B;CACjD,IAAI,SAAS;CAEb,KAAK,MAAM,QAAQ,OACjB,UAAU,OAAO,aAAa,IAAI;CAGpC,OAAO,KAAK,MAAM,CAAC,CAChB,WAAW,KAAK,GAAG,CAAC,CACpB,WAAW,KAAK,GAAG,CAAC,CACpB,QAAQ,OAAO,EAAE;AACtB;AAEA,MAAa,wBACX,iBAAyB,WAAW,WACzB;CACX,IAAI,CAAC,gBAAgB,iBACnB,MAAM,IAAIA,uBAAAA,qBACR,0DACF;CAGF,MAAM,QAAQ,IAAI,WAAW,WAAW;CAExC,eAAe,gBAAgB,KAAK;CAEpC,OAAO,YAAY,KAAK;AAC1B;AAEA,MAAa,mBAAmB,OAC9B,OACA,iBAAyB,WAAW,WAChB;CACpB,IAAI,CAAC,SAAS,MAAM,SAAS,IAC3B,MAAM,IAAIA,uBAAAA,qBAAqB,wBAAwB;CAGzD,IAAI,CAAC,gBAAgB,QACnB,MAAM,IAAIA,uBAAAA,qBAAqB,sCAAsC;CAGvE,MAAM,SAAS,MAAM,eAAe,OAAO,OACzC,WACA,IAAI,YAAY,CAAC,CAAC,OAAO,KAAK,CAChC;CAEA,OAAO,YAAY,IAAI,WAAW,MAAM,CAAC;AAC3C;AAEA,MAAa,qBACX,iBAAyB,WAAW,WACzB;CACX,IAAI,CAAC,gBAAgB,YACnB,MAAM,IAAIA,uBAAAA,qBACR,wDACF;CAGF,OAAO,eAAe,WAAW;AACnC"}
@@ -0,0 +1,3 @@
1
+ export declare const generateSessionToken: (cryptoProvider?: Crypto) => string;
2
+ export declare const hashSessionToken: (token: string, cryptoProvider?: Crypto) => Promise<string>;
3
+ export declare const generateSessionId: (cryptoProvider?: Crypto) => string;
@@ -0,0 +1,3 @@
1
+ export declare const generateSessionToken: (cryptoProvider?: Crypto) => string;
2
+ export declare const hashSessionToken: (token: string, cryptoProvider?: Crypto) => Promise<string>;
3
+ export declare const generateSessionId: (cryptoProvider?: Crypto) => string;
@@ -0,0 +1,27 @@
1
+ import { SessionSecurityError } from "../errors/session.errors.js";
2
+ // src/utils/session-crypto.ts
3
+ const TOKEN_BYTES = 32;
4
+ const toBase64Url = (bytes) => {
5
+ let binary = "";
6
+ for (const byte of bytes) binary += String.fromCharCode(byte);
7
+ return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
8
+ };
9
+ const generateSessionToken = (cryptoProvider = globalThis.crypto) => {
10
+ if (!cryptoProvider?.getRandomValues) throw new SessionSecurityError("A cryptographically secure random generator is required.");
11
+ const bytes = new Uint8Array(TOKEN_BYTES);
12
+ cryptoProvider.getRandomValues(bytes);
13
+ return toBase64Url(bytes);
14
+ };
15
+ const hashSessionToken = async (token, cryptoProvider = globalThis.crypto) => {
16
+ if (!token || token.length < 32) throw new SessionSecurityError("Invalid session token.");
17
+ if (!cryptoProvider?.subtle) throw new SessionSecurityError("Web Crypto SubtleCrypto is required.");
18
+ const digest = await cryptoProvider.subtle.digest("SHA-256", new TextEncoder().encode(token));
19
+ return toBase64Url(new Uint8Array(digest));
20
+ };
21
+ const generateSessionId = (cryptoProvider = globalThis.crypto) => {
22
+ if (!cryptoProvider?.randomUUID) throw new SessionSecurityError("A cryptographically secure UUID generator is required.");
23
+ return cryptoProvider.randomUUID();
24
+ };
25
+ export { generateSessionId, generateSessionToken, hashSessionToken };
26
+
27
+ //# sourceMappingURL=session-crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-crypto.js","names":[],"sources":["../../src/utils/session-crypto.ts"],"sourcesContent":["import { SessionSecurityError } from \"../errors/session.errors\";\n\nconst TOKEN_BYTES = 32;\n\nconst toBase64Url = (bytes: Uint8Array): string => {\n let binary = \"\";\n\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n\n return btoa(binary)\n .replaceAll(\"+\", \"-\")\n .replaceAll(\"/\", \"_\")\n .replace(/=+$/, \"\");\n};\n\nexport const generateSessionToken = (\n cryptoProvider: Crypto = globalThis.crypto,\n): string => {\n if (!cryptoProvider?.getRandomValues) {\n throw new SessionSecurityError(\n \"A cryptographically secure random generator is required.\",\n );\n }\n\n const bytes = new Uint8Array(TOKEN_BYTES);\n\n cryptoProvider.getRandomValues(bytes);\n\n return toBase64Url(bytes);\n};\n\nexport const hashSessionToken = async (\n token: string,\n cryptoProvider: Crypto = globalThis.crypto,\n): Promise<string> => {\n if (!token || token.length < 32) {\n throw new SessionSecurityError(\"Invalid session token.\");\n }\n\n if (!cryptoProvider?.subtle) {\n throw new SessionSecurityError(\"Web Crypto SubtleCrypto is required.\");\n }\n\n const digest = await cryptoProvider.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(token),\n );\n\n return toBase64Url(new Uint8Array(digest));\n};\n\nexport const generateSessionId = (\n cryptoProvider: Crypto = globalThis.crypto,\n): string => {\n if (!cryptoProvider?.randomUUID) {\n throw new SessionSecurityError(\n \"A cryptographically secure UUID generator is required.\",\n );\n }\n\n return cryptoProvider.randomUUID();\n};\n"],"mappings":";;AAEA,MAAM,cAAc;AAEpB,MAAM,eAAe,UAA8B;CACjD,IAAI,SAAS;CAEb,KAAK,MAAM,QAAQ,OACjB,UAAU,OAAO,aAAa,IAAI;CAGpC,OAAO,KAAK,MAAM,CAAC,CAChB,WAAW,KAAK,GAAG,CAAC,CACpB,WAAW,KAAK,GAAG,CAAC,CACpB,QAAQ,OAAO,EAAE;AACtB;AAEA,MAAa,wBACX,iBAAyB,WAAW,WACzB;CACX,IAAI,CAAC,gBAAgB,iBACnB,MAAM,IAAI,qBACR,0DACF;CAGF,MAAM,QAAQ,IAAI,WAAW,WAAW;CAExC,eAAe,gBAAgB,KAAK;CAEpC,OAAO,YAAY,KAAK;AAC1B;AAEA,MAAa,mBAAmB,OAC9B,OACA,iBAAyB,WAAW,WAChB;CACpB,IAAI,CAAC,SAAS,MAAM,SAAS,IAC3B,MAAM,IAAI,qBAAqB,wBAAwB;CAGzD,IAAI,CAAC,gBAAgB,QACnB,MAAM,IAAI,qBAAqB,sCAAsC;CAGvE,MAAM,SAAS,MAAM,eAAe,OAAO,OACzC,WACA,IAAI,YAAY,CAAC,CAAC,OAAO,KAAK,CAChC;CAEA,OAAO,YAAY,IAAI,WAAW,MAAM,CAAC;AAC3C;AAEA,MAAa,qBACX,iBAAyB,WAAW,WACzB;CACX,IAAI,CAAC,gBAAgB,YACnB,MAAM,IAAI,qBACR,wDACF;CAGF,OAAO,eAAe,WAAW;AACnC"}
package/package.json CHANGED
@@ -1,6 +1,50 @@
1
1
  {
2
2
  "name": "@puzzle-ui/session",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.5.1",
4
+ "description": "Secure, database-independent session management for Puzzle Auth.",
5
+ "homepage": "https://puzzle-ui.com",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/senorprogramador/puzzle.git"
9
+ },
10
+ "license": "MIT",
11
+ "publishConfig": {
12
+ "access": "public",
13
+ "registry": "https://registry.npmjs.org/"
14
+ },
15
+ "type": "module",
16
+ "main": "./dist/index.cjs",
17
+ "module": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "import": {
22
+ "types": "./dist/index.d.ts",
23
+ "default": "./dist/index.js"
24
+ },
25
+ "require": {
26
+ "types": "./dist/index.d.cts",
27
+ "default": "./dist/index.cjs"
28
+ }
29
+ }
30
+ },
31
+ "files": [
32
+ "dist"
33
+ ],
34
+ "sideEffects": false,
35
+ "scripts": {
36
+ "build": "tsdown",
37
+ "typecheck": "tsc --noEmit -p .",
38
+ "vitest": "node --test",
39
+ "test": "node --test",
40
+ "clean": "rm -rf dist",
41
+ "prepack": "tsdown"
42
+ },
43
+ "devDependencies": {
44
+ "tsdown": "^0.23.1",
45
+ "typescript": "^7.0.2"
46
+ },
47
+ "dependencies": {
48
+ "@puzzle-ui/storage": "^0.5.1"
49
+ }
50
+ }