@grackle-ai/auth 0.72.3
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 +26 -0
- package/dist/api-key.d.ts +12 -0
- package/dist/api-key.d.ts.map +1 -0
- package/dist/api-key.js +65 -0
- package/dist/api-key.js.map +1 -0
- package/dist/auth-context.d.ts +20 -0
- package/dist/auth-context.d.ts.map +1 -0
- package/dist/auth-context.js +2 -0
- package/dist/auth-context.js.map +1 -0
- package/dist/auth-logger.d.ts +12 -0
- package/dist/auth-logger.d.ts.map +1 -0
- package/dist/auth-logger.js +24 -0
- package/dist/auth-logger.js.map +1 -0
- package/dist/auth-middleware.d.ts +16 -0
- package/dist/auth-middleware.d.ts.map +1 -0
- package/dist/auth-middleware.js +93 -0
- package/dist/auth-middleware.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth-token.d.ts +38 -0
- package/dist/oauth-token.d.ts.map +1 -0
- package/dist/oauth-token.js +101 -0
- package/dist/oauth-token.js.map +1 -0
- package/dist/oauth.d.ts +102 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +225 -0
- package/dist/oauth.js.map +1 -0
- package/dist/pairing.d.ts +22 -0
- package/dist/pairing.d.ts.map +1 -0
- package/dist/pairing.js +136 -0
- package/dist/pairing.js.map +1 -0
- package/dist/scoped-token.d.ts +55 -0
- package/dist/scoped-token.d.ts.map +1 -0
- package/dist/scoped-token.js +131 -0
- package/dist/scoped-token.js.map +1 -0
- package/dist/security-headers.d.ts +17 -0
- package/dist/security-headers.d.ts.map +1 -0
- package/dist/security-headers.js +31 -0
- package/dist/security-headers.js.map +1 -0
- package/dist/session.d.ts +36 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +143 -0
- package/dist/session.js.map +1 -0
- package/dist/tsdoc-metadata.json +11 -0
- package/package.json +43 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Name of the session cookie sent to browsers. */
|
|
2
|
+
export declare const SESSION_COOKIE_NAME: string;
|
|
3
|
+
/** Start the periodic session cleanup timer. Call once on server startup. */
|
|
4
|
+
export declare function startSessionCleanup(): void;
|
|
5
|
+
/** Stop the periodic session cleanup timer. */
|
|
6
|
+
export declare function stopSessionCleanup(): void;
|
|
7
|
+
/**
|
|
8
|
+
* Create a new session and return the Set-Cookie header value.
|
|
9
|
+
*
|
|
10
|
+
* The cookie format is `<sessionId>.<signature>` where the signature
|
|
11
|
+
* is an HMAC-SHA256 of the session ID using the API key as secret.
|
|
12
|
+
*
|
|
13
|
+
* When `options.secure` is true the cookie includes the `Secure` flag,
|
|
14
|
+
* which tells browsers to only send it over HTTPS. This should be
|
|
15
|
+
* enabled when the server is network-accessible (`--allow-network`)
|
|
16
|
+
* behind a TLS-terminating reverse proxy.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createSession(apiKey: string, options?: {
|
|
19
|
+
secure?: boolean;
|
|
20
|
+
}): string;
|
|
21
|
+
/**
|
|
22
|
+
* Parse a raw Cookie header into key-value pairs.
|
|
23
|
+
*
|
|
24
|
+
* Handles the standard `name=value; name2=value2` format.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseCookies(header: string): Record<string, string>;
|
|
27
|
+
/**
|
|
28
|
+
* Validate a session cookie from a raw Cookie header string.
|
|
29
|
+
*
|
|
30
|
+
* Returns true if the cookie contains a valid, non-expired session
|
|
31
|
+
* with a correct HMAC signature.
|
|
32
|
+
*/
|
|
33
|
+
export declare function validateSessionCookie(cookieHeader: string, apiKey: string): boolean;
|
|
34
|
+
/** Remove all sessions (useful for testing). */
|
|
35
|
+
export declare function clearSessions(): void;
|
|
36
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAEA,mDAAmD;AACnD,eAAO,MAAM,mBAAmB,EAAE,MAA0B,CAAC;AAqB7D,6EAA6E;AAC7E,wBAAgB,mBAAmB,IAAI,IAAI,CAc1C;AAED,+CAA+C;AAC/C,wBAAgB,kBAAkB,IAAI,IAAI,CAKzC;AAUD;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,CAwBpF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAenE;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAwCnF;AAED,gDAAgD;AAChD,wBAAgB,aAAa,IAAI,IAAI,CAEpC"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { randomBytes, createHmac } from "node:crypto";
|
|
2
|
+
/** Name of the session cookie sent to browsers. */
|
|
3
|
+
export const SESSION_COOKIE_NAME = "grackle_session";
|
|
4
|
+
/** Default session lifetime: 24 hours. */
|
|
5
|
+
const SESSION_TTL_MS = 24 * 60 * 60 * 1000;
|
|
6
|
+
/** Byte length of the random session identifier. */
|
|
7
|
+
const SESSION_ID_BYTES = 32;
|
|
8
|
+
/** Interval at which expired sessions are cleaned up. */
|
|
9
|
+
const SESSION_CLEANUP_INTERVAL_MS = 60 * 1000;
|
|
10
|
+
/** In-memory session store keyed by session ID. */
|
|
11
|
+
const sessions = new Map();
|
|
12
|
+
let cleanupTimer;
|
|
13
|
+
/** Start the periodic session cleanup timer. Call once on server startup. */
|
|
14
|
+
export function startSessionCleanup() {
|
|
15
|
+
if (cleanupTimer) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
cleanupTimer = setInterval(() => {
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
for (const [id, record] of sessions) {
|
|
21
|
+
if (now > record.expiresAt) {
|
|
22
|
+
sessions.delete(id);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}, SESSION_CLEANUP_INTERVAL_MS);
|
|
26
|
+
// Allow the process to exit even if the timer is still running
|
|
27
|
+
cleanupTimer.unref();
|
|
28
|
+
}
|
|
29
|
+
/** Stop the periodic session cleanup timer. */
|
|
30
|
+
export function stopSessionCleanup() {
|
|
31
|
+
if (cleanupTimer) {
|
|
32
|
+
clearInterval(cleanupTimer);
|
|
33
|
+
cleanupTimer = undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Create an HMAC-SHA256 signature of a value using the given secret.
|
|
38
|
+
* Returns a hex-encoded string.
|
|
39
|
+
*/
|
|
40
|
+
function sign(value, secret) {
|
|
41
|
+
return createHmac("sha256", secret).update(value).digest("hex");
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Create a new session and return the Set-Cookie header value.
|
|
45
|
+
*
|
|
46
|
+
* The cookie format is `<sessionId>.<signature>` where the signature
|
|
47
|
+
* is an HMAC-SHA256 of the session ID using the API key as secret.
|
|
48
|
+
*
|
|
49
|
+
* When `options.secure` is true the cookie includes the `Secure` flag,
|
|
50
|
+
* which tells browsers to only send it over HTTPS. This should be
|
|
51
|
+
* enabled when the server is network-accessible (`--allow-network`)
|
|
52
|
+
* behind a TLS-terminating reverse proxy.
|
|
53
|
+
*/
|
|
54
|
+
export function createSession(apiKey, options) {
|
|
55
|
+
const sessionId = randomBytes(SESSION_ID_BYTES).toString("hex");
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
sessions.set(sessionId, {
|
|
58
|
+
createdAt: now,
|
|
59
|
+
expiresAt: now + SESSION_TTL_MS,
|
|
60
|
+
});
|
|
61
|
+
const signature = sign(sessionId, apiKey);
|
|
62
|
+
const cookieValue = `${sessionId}.${signature}`;
|
|
63
|
+
const maxAge = Math.floor(SESSION_TTL_MS / 1000);
|
|
64
|
+
const parts = [
|
|
65
|
+
`${SESSION_COOKIE_NAME}=${cookieValue}`,
|
|
66
|
+
"HttpOnly",
|
|
67
|
+
"SameSite=Lax",
|
|
68
|
+
"Path=/",
|
|
69
|
+
`Max-Age=${maxAge}`,
|
|
70
|
+
];
|
|
71
|
+
if (options?.secure) {
|
|
72
|
+
parts.push("Secure");
|
|
73
|
+
}
|
|
74
|
+
return parts.join("; ");
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Parse a raw Cookie header into key-value pairs.
|
|
78
|
+
*
|
|
79
|
+
* Handles the standard `name=value; name2=value2` format.
|
|
80
|
+
*/
|
|
81
|
+
export function parseCookies(header) {
|
|
82
|
+
const result = {};
|
|
83
|
+
if (!header) {
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
for (const pair of header.split(";")) {
|
|
87
|
+
const eqIndex = pair.indexOf("=");
|
|
88
|
+
if (eqIndex === -1) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const name = pair.slice(0, eqIndex).trim();
|
|
92
|
+
const value = pair.slice(eqIndex + 1).trim();
|
|
93
|
+
result[name] = value;
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Validate a session cookie from a raw Cookie header string.
|
|
99
|
+
*
|
|
100
|
+
* Returns true if the cookie contains a valid, non-expired session
|
|
101
|
+
* with a correct HMAC signature.
|
|
102
|
+
*/
|
|
103
|
+
export function validateSessionCookie(cookieHeader, apiKey) {
|
|
104
|
+
const cookies = parseCookies(cookieHeader);
|
|
105
|
+
const raw = cookies[SESSION_COOKIE_NAME];
|
|
106
|
+
if (!raw) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const dotIndex = raw.lastIndexOf(".");
|
|
110
|
+
if (dotIndex === -1) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
const sessionId = raw.slice(0, dotIndex);
|
|
114
|
+
const providedSignature = raw.slice(dotIndex + 1);
|
|
115
|
+
const expectedSignature = sign(sessionId, apiKey);
|
|
116
|
+
// Constant-time comparison for the signature
|
|
117
|
+
if (providedSignature.length !== expectedSignature.length) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
let diff = 0;
|
|
121
|
+
for (let i = 0; i < expectedSignature.length; i++) {
|
|
122
|
+
// eslint-disable-next-line no-bitwise
|
|
123
|
+
diff |= providedSignature.charCodeAt(i) ^ expectedSignature.charCodeAt(i);
|
|
124
|
+
}
|
|
125
|
+
if (diff !== 0) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
// Check session exists and hasn't expired
|
|
129
|
+
const record = sessions.get(sessionId);
|
|
130
|
+
if (!record) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
if (Date.now() > record.expiresAt) {
|
|
134
|
+
sessions.delete(sessionId);
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
/** Remove all sessions (useful for testing). */
|
|
140
|
+
export function clearSessions() {
|
|
141
|
+
sessions.clear();
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// This file is read by tools that parse documentation comments conforming to the TSDoc standard.
|
|
2
|
+
// It should be published with your NPM package. It should not be tracked by Git.
|
|
3
|
+
{
|
|
4
|
+
"tsdocVersion": "0.12",
|
|
5
|
+
"toolPackages": [
|
|
6
|
+
{
|
|
7
|
+
"packageName": "@microsoft/api-extractor",
|
|
8
|
+
"packageVersion": "7.57.7"
|
|
9
|
+
}
|
|
10
|
+
]
|
|
11
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@grackle-ai/auth",
|
|
3
|
+
"version": "0.72.3",
|
|
4
|
+
"description": "Authentication and authorization primitives for Grackle",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/nick-pape/grackle.git",
|
|
9
|
+
"directory": "packages/auth"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"grackle",
|
|
13
|
+
"auth",
|
|
14
|
+
"oauth",
|
|
15
|
+
"pairing",
|
|
16
|
+
"security"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22.0.0"
|
|
20
|
+
},
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "dist/index.js",
|
|
23
|
+
"types": "dist/index.d.ts",
|
|
24
|
+
"files": [
|
|
25
|
+
"dist/"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@grackle-ai/common": "0.72.3"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@rushstack/heft": "1.2.7",
|
|
32
|
+
"@types/node": "^22.0.0",
|
|
33
|
+
"vitest": "^3.1.1",
|
|
34
|
+
"@grackle-ai/heft-rig": "0.0.1"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "heft build --clean",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"clean": "heft clean",
|
|
40
|
+
"_phase:build": "heft run --only build -- --clean",
|
|
41
|
+
"_phase:test": "vitest run"
|
|
42
|
+
}
|
|
43
|
+
}
|