@simple-login/sdk 1.0.0 → 1.2.0

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/index.cjs CHANGED
@@ -27,6 +27,9 @@ __export(index_exports, {
27
27
  });
28
28
  module.exports = __toCommonJS(index_exports);
29
29
 
30
+ // src/client.ts
31
+ var import_jose = require("jose");
32
+
30
33
  // src/errors.ts
31
34
  var SousaError = class extends Error {
32
35
  constructor(message, code, statusCode) {
@@ -56,6 +59,17 @@ function generateState() {
56
59
  crypto.getRandomValues(array);
57
60
  return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
58
61
  }
62
+ function generateCodeVerifier() {
63
+ const array = new Uint8Array(32);
64
+ crypto.getRandomValues(array);
65
+ return btoa(String.fromCharCode(...array)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
66
+ }
67
+ async function generateCodeChallenge(verifier) {
68
+ const encoder = new TextEncoder();
69
+ const data = encoder.encode(verifier);
70
+ const hash = await crypto.subtle.digest("SHA-256", data);
71
+ return btoa(String.fromCharCode(...new Uint8Array(hash))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
72
+ }
59
73
  function getEnv(key) {
60
74
  if (typeof process !== "undefined" && process.env) {
61
75
  return process.env[key];
@@ -66,6 +80,20 @@ function parseCookie(cookieHeader, name) {
66
80
  const match = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
67
81
  return match ? decodeURIComponent(match[1]) : void 0;
68
82
  }
83
+ var publicKeyCache = /* @__PURE__ */ new Map();
84
+ var PUBLIC_KEY_CACHE_TTL = 60 * 60 * 1e3;
85
+ function createTokenCookies(tokens) {
86
+ return [
87
+ `SIMPLELOGIN_ACCESS_TOKEN=${tokens.access_token}; HttpOnly; Secure; SameSite=Lax; Path=/`,
88
+ `SIMPLELOGIN_REFRESH_TOKEN=${tokens.refresh_token}; HttpOnly; Secure; SameSite=Lax; Path=/`
89
+ ];
90
+ }
91
+ function clearTokenCookies() {
92
+ return [
93
+ "SIMPLELOGIN_ACCESS_TOKEN=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0",
94
+ "SIMPLELOGIN_REFRESH_TOKEN=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0"
95
+ ];
96
+ }
69
97
  var SimpleLogin = class {
70
98
  clientId;
71
99
  clientSecret;
@@ -93,50 +121,57 @@ var SimpleLogin = class {
93
121
  }
94
122
  }
95
123
  /**
96
- * Returns a Response that redirects to the authorization URL with the state cookie set.
124
+ * Returns a Response that redirects to the authorization URL with state and PKCE cookies set.
97
125
  * @param options - Optional scopes or custom state
98
126
  * @returns A 302 redirect Response ready to be returned from your route handler
99
127
  */
100
- redirectToAuth(options = {}) {
101
- const { url, cookie } = this.getAuthorizationUrl(options);
102
- return new Response(null, {
103
- status: 302,
104
- headers: {
105
- Location: url,
106
- "Set-Cookie": cookie
107
- }
108
- });
128
+ async redirectToAuth(options = {}) {
129
+ const { url, cookies } = await this.getAuthorizationUrl(options);
130
+ const headers = new Headers();
131
+ headers.set("Location", url);
132
+ for (const cookie of cookies) {
133
+ headers.append("Set-Cookie", cookie);
134
+ }
135
+ return new Response(null, { status: 302, headers });
109
136
  }
110
137
  /**
111
138
  * Generate the authorization URL to redirect users for sign-in
112
- * @returns The authorization URL, state parameter, and a ready-to-use Set-Cookie header value
139
+ * @returns The authorization URL, state, code verifier, and ready-to-use Set-Cookie header values
113
140
  */
114
- getAuthorizationUrl(options = {}) {
141
+ async getAuthorizationUrl(options = {}) {
115
142
  const state = options.state ?? generateState();
143
+ const codeVerifier = generateCodeVerifier();
144
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
116
145
  const params = new URLSearchParams({
117
146
  client_id: this.clientId,
118
147
  redirect_uri: this.redirectUri,
119
148
  response_type: "code",
120
- state
149
+ state,
150
+ code_challenge: codeChallenge,
151
+ code_challenge_method: "S256"
121
152
  });
122
153
  if (options.scopes?.length) {
123
154
  params.set("scope", options.scopes.join(" "));
124
155
  }
125
- const cookie = `SIMPLELOGIN_STATE=${state}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`;
156
+ const cookies = [
157
+ `SIMPLELOGIN_STATE=${state}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`,
158
+ `SIMPLELOGIN_CODE_VERIFIER=${codeVerifier}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`
159
+ ];
126
160
  return {
127
161
  url: `${this.baseUrl}/v1/auth/authorize?${params.toString()}`,
128
162
  state,
129
- cookie
163
+ codeVerifier,
164
+ cookies
130
165
  };
131
166
  }
132
167
  /**
133
- * Verify the OAuth callback request and exchange the code for tokens.
134
- * Automatically extracts state from SIMPLELOGIN_STATE cookie and code/state from URL.
168
+ * Handle the OAuth callback: verify state, exchange code, fetch user info, and return a redirect Response.
135
169
  * @param request - The incoming Request object from the callback
136
- * @returns The token response if verification succeeds
170
+ * @param redirectTo - URL to redirect to after successful authentication (default: '/')
171
+ * @returns CallbackResult with redirect Response (cookies set) and user info to store
137
172
  * @throws AuthorizationError if state is missing or doesn't match
138
173
  */
139
- async handleCallback(request) {
174
+ async handleCallback(request, redirectTo = "/") {
140
175
  const url = new URL(request.url);
141
176
  const code = url.searchParams.get("code");
142
177
  const state = url.searchParams.get("state");
@@ -148,18 +183,41 @@ var SimpleLogin = class {
148
183
  }
149
184
  const cookieHeader = request.headers.get("cookie") ?? "";
150
185
  const expectedState = parseCookie(cookieHeader, "SIMPLELOGIN_STATE");
186
+ const codeVerifier = parseCookie(cookieHeader, "SIMPLELOGIN_CODE_VERIFIER");
151
187
  if (!expectedState) {
152
188
  throw new AuthorizationError("Missing SIMPLELOGIN_STATE cookie");
153
189
  }
154
190
  if (state !== expectedState) {
155
191
  throw new AuthorizationError("Invalid state parameter");
156
192
  }
157
- return this.exchangeCode(code);
193
+ if (!codeVerifier) {
194
+ throw new AuthorizationError("Missing SIMPLELOGIN_CODE_VERIFIER cookie");
195
+ }
196
+ const tokens = await this.exchangeCode(code, codeVerifier);
197
+ const user = await this.getUserInfo(tokens.access_token);
198
+ const cookies = createTokenCookies(tokens);
199
+ const headers = new Headers();
200
+ headers.set("Location", redirectTo);
201
+ for (const cookie of cookies) {
202
+ headers.append("Set-Cookie", cookie);
203
+ }
204
+ headers.append(
205
+ "Set-Cookie",
206
+ "SIMPLELOGIN_STATE=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0"
207
+ );
208
+ headers.append(
209
+ "Set-Cookie",
210
+ "SIMPLELOGIN_CODE_VERIFIER=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0"
211
+ );
212
+ const response = new Response(null, { status: 302, headers });
213
+ return { response, user };
158
214
  }
159
215
  /**
160
216
  * Exchange an authorization code for access and refresh tokens
217
+ * @param code - The authorization code from the callback
218
+ * @param codeVerifier - The PKCE code verifier
161
219
  */
162
- async exchangeCode(code) {
220
+ async exchangeCode(code, codeVerifier) {
163
221
  const response = await fetch(`${this.baseUrl}/v1/auth/token`, {
164
222
  method: "POST",
165
223
  headers: {
@@ -170,7 +228,8 @@ var SimpleLogin = class {
170
228
  client_id: this.clientId,
171
229
  client_secret: this.clientSecret,
172
230
  redirect_uri: this.redirectUri,
173
- code
231
+ code,
232
+ code_verifier: codeVerifier
174
233
  })
175
234
  });
176
235
  if (!response.ok) {
@@ -215,6 +274,98 @@ var SimpleLogin = class {
215
274
  }
216
275
  return response.json();
217
276
  }
277
+ /**
278
+ * Fetch and cache the public key for JWT verification
279
+ */
280
+ async getPublicKey() {
281
+ const cached = publicKeyCache.get(this.clientId);
282
+ if (cached && Date.now() - cached.fetchedAt < PUBLIC_KEY_CACHE_TTL) {
283
+ return cached.key;
284
+ }
285
+ const response = await fetch(`${this.baseUrl}/api/applications/${this.clientId}/public-key`);
286
+ if (!response.ok) {
287
+ throw new AuthorizationError("Failed to fetch public key");
288
+ }
289
+ const pem = await response.text();
290
+ const key = await (0, import_jose.importSPKI)(pem, "RS256");
291
+ publicKeyCache.set(this.clientId, { key, fetchedAt: Date.now() });
292
+ return key;
293
+ }
294
+ /**
295
+ * Authenticate a request by verifying the access token from cookies.
296
+ * No network calls except when refreshing expired tokens.
297
+ * @param request - The incoming Request object
298
+ * @param options - Optional settings for CSRF protection
299
+ * @returns AuthResult if authenticated, null if not authenticated
300
+ */
301
+ async authenticate(request, options) {
302
+ const method = request.method.toUpperCase();
303
+ if (method !== "GET" && method !== "HEAD" && options?.allowedOrigin) {
304
+ const origin = request.headers.get("origin") || request.headers.get("referer");
305
+ if (!origin?.startsWith(options.allowedOrigin)) {
306
+ return null;
307
+ }
308
+ }
309
+ const cookieHeader = request.headers.get("cookie") ?? "";
310
+ const accessToken = parseCookie(cookieHeader, "SIMPLELOGIN_ACCESS_TOKEN");
311
+ const refreshTokenValue = parseCookie(cookieHeader, "SIMPLELOGIN_REFRESH_TOKEN");
312
+ if (!accessToken) {
313
+ return null;
314
+ }
315
+ const publicKey = await this.getPublicKey();
316
+ try {
317
+ await (0, import_jose.jwtVerify)(accessToken, publicKey);
318
+ const claims = (0, import_jose.decodeJwt)(accessToken);
319
+ return { claims, accessToken };
320
+ } catch (error) {
321
+ const isExpired = error instanceof Error && "code" in error && error.code === "ERR_JWT_EXPIRED";
322
+ if (!isExpired || !refreshTokenValue) {
323
+ return null;
324
+ }
325
+ try {
326
+ const tokens = await this.refreshToken(refreshTokenValue);
327
+ const claims = (0, import_jose.decodeJwt)(tokens.access_token);
328
+ const cookies = createTokenCookies(tokens);
329
+ return { claims, accessToken: tokens.access_token, cookies };
330
+ } catch {
331
+ return null;
332
+ }
333
+ }
334
+ }
335
+ /**
336
+ * Revoke a refresh token on the IdP
337
+ */
338
+ async revokeToken(refreshToken) {
339
+ await fetch(`${this.baseUrl}/v1/auth/revoke`, {
340
+ method: "POST",
341
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
342
+ body: new URLSearchParams({
343
+ token: refreshToken,
344
+ client_id: this.clientId,
345
+ client_secret: this.clientSecret
346
+ })
347
+ });
348
+ }
349
+ /**
350
+ * Logout: revoke refresh token on IdP and clear cookies.
351
+ * @param request - The incoming Request object (to read refresh token from cookies)
352
+ * @param redirectTo - URL to redirect to after logout (default: '/')
353
+ */
354
+ async logout(request, redirectTo = "/") {
355
+ const cookieHeader = request.headers.get("cookie") ?? "";
356
+ const refreshToken = parseCookie(cookieHeader, "SIMPLELOGIN_REFRESH_TOKEN");
357
+ if (refreshToken) {
358
+ this.revokeToken(refreshToken).catch(() => {
359
+ });
360
+ }
361
+ const cookies = clearTokenCookies();
362
+ const headers = new Headers();
363
+ headers.set("Location", redirectTo);
364
+ for (const cookie of cookies) {
365
+ headers.append("Set-Cookie", cookie);
366
+ }
367
+ return new Response(null, { status: 302, headers });
368
+ }
218
369
  };
219
370
  // Annotate the CommonJS export names for ESM import in node:
220
371
  0 && (module.exports = {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/client.ts"],"sourcesContent":["export { SimpleLogin } from './client'\nexport { AuthorizationError, SousaError, TokenError } from './errors'\nexport type {\n AuthorizationUrlOptions,\n AuthorizationUrlResult,\n SimpleLoginConfig,\n TokenResponse,\n UserInfo,\n} from './types'\n","export class SousaError extends Error {\n constructor(\n message: string,\n public code: string,\n public statusCode?: number\n ) {\n super(message);\n this.name = \"SousaError\";\n }\n}\n\nexport class AuthorizationError extends SousaError {\n constructor(message: string) {\n super(message, \"AUTHORIZATION_ERROR\", 401);\n this.name = \"AuthorizationError\";\n }\n}\n\nexport class TokenError extends SousaError {\n constructor(message: string) {\n super(message, \"TOKEN_ERROR\", 400);\n this.name = \"TokenError\";\n }\n}\n","import { AuthorizationError, TokenError } from './errors'\nimport type {\n AuthorizationUrlOptions,\n AuthorizationUrlResult,\n SimpleLoginConfig,\n TokenResponse,\n UserInfo,\n} from './types'\n\ndeclare const __SIMPLELOGIN_BASE_URL__: string\n\nconst BASE_URL = __SIMPLELOGIN_BASE_URL__\n\nfunction generateState(): string {\n const array = new Uint8Array(32)\n crypto.getRandomValues(array)\n return Array.from(array, (b) => b.toString(16).padStart(2, '0')).join('')\n}\n\nfunction getEnv(key: string): string | undefined {\n if (typeof process !== 'undefined' && process.env) {\n return process.env[key]\n }\n return undefined\n}\n\nfunction parseCookie(cookieHeader: string, name: string): string | undefined {\n const match = cookieHeader.match(new RegExp(`(?:^|;\\\\s*)${name}=([^;]*)`))\n return match ? decodeURIComponent(match[1]) : undefined\n}\n\nexport class SimpleLogin {\n private clientId: string\n private clientSecret: string\n private redirectUri: string\n private baseUrl: string\n\n constructor(config: SimpleLoginConfig = {}) {\n this.clientId = config.clientId ?? getEnv('SIMPLELOGIN_CLIENT_ID') ?? ''\n this.clientSecret = config.clientSecret ?? getEnv('SIMPLELOGIN_CLIENT_SECRET') ?? ''\n this.redirectUri = config.redirectUri ?? getEnv('SIMPLELOGIN_REDIRECT_URI') ?? ''\n this.baseUrl = BASE_URL\n\n if (!this.clientId) {\n throw new Error(\n 'clientId is required. Pass it in config or set SIMPLELOGIN_CLIENT_ID env var.',\n )\n }\n if (!this.clientSecret) {\n throw new Error(\n 'clientSecret is required. Pass it in config or set SIMPLELOGIN_CLIENT_SECRET env var.',\n )\n }\n if (!this.redirectUri) {\n throw new Error(\n 'redirectUri is required. Pass it in config or set SIMPLELOGIN_REDIRECT_URI env var.',\n )\n }\n }\n\n /**\n * Returns a Response that redirects to the authorization URL with the state cookie set.\n * @param options - Optional scopes or custom state\n * @returns A 302 redirect Response ready to be returned from your route handler\n */\n redirectToAuth(options: AuthorizationUrlOptions = {}): Response {\n const { url, cookie } = this.getAuthorizationUrl(options)\n return new Response(null, {\n status: 302,\n headers: {\n Location: url,\n 'Set-Cookie': cookie,\n },\n })\n }\n\n /**\n * Generate the authorization URL to redirect users for sign-in\n * @returns The authorization URL, state parameter, and a ready-to-use Set-Cookie header value\n */\n getAuthorizationUrl(options: AuthorizationUrlOptions = {}): AuthorizationUrlResult {\n const state = options.state ?? generateState()\n\n const params = new URLSearchParams({\n client_id: this.clientId,\n redirect_uri: this.redirectUri,\n response_type: 'code',\n state,\n })\n\n if (options.scopes?.length) {\n params.set('scope', options.scopes.join(' '))\n }\n\n const cookie = `SIMPLELOGIN_STATE=${state}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`\n\n return {\n url: `${this.baseUrl}/v1/auth/authorize?${params.toString()}`,\n state,\n cookie,\n }\n }\n\n /**\n * Verify the OAuth callback request and exchange the code for tokens.\n * Automatically extracts state from SIMPLELOGIN_STATE cookie and code/state from URL.\n * @param request - The incoming Request object from the callback\n * @returns The token response if verification succeeds\n * @throws AuthorizationError if state is missing or doesn't match\n */\n async handleCallback(request: Request): Promise<TokenResponse> {\n const url = new URL(request.url)\n const code = url.searchParams.get('code')\n const state = url.searchParams.get('state')\n\n if (!code) {\n throw new AuthorizationError('Missing authorization code in callback')\n }\n\n if (!state) {\n throw new AuthorizationError('Missing state parameter in callback')\n }\n\n const cookieHeader = request.headers.get('cookie') ?? ''\n const expectedState = parseCookie(cookieHeader, 'SIMPLELOGIN_STATE')\n\n if (!expectedState) {\n throw new AuthorizationError('Missing SIMPLELOGIN_STATE cookie')\n }\n\n if (state !== expectedState) {\n throw new AuthorizationError('Invalid state parameter')\n }\n\n return this.exchangeCode(code)\n }\n\n /**\n * Exchange an authorization code for access and refresh tokens\n */\n async exchangeCode(code: string): Promise<TokenResponse> {\n const response = await fetch(`${this.baseUrl}/v1/auth/token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: this.clientId,\n client_secret: this.clientSecret,\n redirect_uri: this.redirectUri,\n code,\n }),\n })\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}))\n throw new TokenError(error.error_description || 'Failed to exchange authorization code')\n }\n\n return response.json()\n }\n\n /**\n * Refresh an access token using a refresh token\n */\n async refreshToken(refreshToken: string): Promise<TokenResponse> {\n const response = await fetch(`${this.baseUrl}/v1/auth/token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: this.clientId,\n client_secret: this.clientSecret,\n refresh_token: refreshToken,\n }),\n })\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}))\n throw new TokenError(error.error_description || 'Failed to refresh token')\n }\n\n return response.json()\n }\n\n /**\n * Get user information using an access token\n */\n async getUserInfo(accessToken: string): Promise<UserInfo> {\n const response = await fetch(`${this.baseUrl}/v1/auth/userinfo`, {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n },\n })\n\n if (!response.ok) {\n throw new AuthorizationError('Failed to fetch user info')\n }\n\n return response.json()\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts","../src/errors.ts"],"sourcesContent":["export { SimpleLogin } from './client'\nexport { AuthorizationError, SousaError, TokenError } from './errors'\nexport type {\n AccessTokenClaims,\n AuthResult,\n AuthorizationUrlOptions,\n AuthorizationUrlResult,\n CallbackResult,\n SimpleLoginConfig,\n TokenResponse,\n UserInfo,\n} from './types'\n","import { decodeJwt, importSPKI, jwtVerify } from 'jose'\nimport { AuthorizationError, TokenError } from './errors'\nimport type {\n AccessTokenClaims,\n AuthResult,\n AuthorizationUrlOptions,\n AuthorizationUrlResult,\n CallbackResult,\n SimpleLoginConfig,\n TokenResponse,\n UserInfo,\n} from './types'\n\ndeclare const __SIMPLELOGIN_BASE_URL__: string\n\nconst BASE_URL = __SIMPLELOGIN_BASE_URL__\n\nfunction generateState(): string {\n const array = new Uint8Array(32)\n crypto.getRandomValues(array)\n return Array.from(array, (b) => b.toString(16).padStart(2, '0')).join('')\n}\n\nfunction generateCodeVerifier(): string {\n const array = new Uint8Array(32)\n crypto.getRandomValues(array)\n // Base64url encode (RFC 7636)\n return btoa(String.fromCharCode(...array))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '')\n}\n\nasync function generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder()\n const data = encoder.encode(verifier)\n const hash = await crypto.subtle.digest('SHA-256', data)\n // Base64url encode the hash\n return btoa(String.fromCharCode(...new Uint8Array(hash)))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '')\n}\n\nfunction getEnv(key: string): string | undefined {\n if (typeof process !== 'undefined' && process.env) {\n return process.env[key]\n }\n return undefined\n}\n\nfunction parseCookie(cookieHeader: string, name: string): string | undefined {\n const match = cookieHeader.match(new RegExp(`(?:^|;\\\\s*)${name}=([^;]*)`))\n return match ? decodeURIComponent(match[1]) : undefined\n}\n\n// Public key cache: clientId -> { key, fetchedAt }\nconst publicKeyCache = new Map<string, { key: CryptoKey; fetchedAt: number }>()\nconst PUBLIC_KEY_CACHE_TTL = 60 * 60 * 1000 // 1 hour\n\nfunction createTokenCookies(tokens: TokenResponse): string[] {\n return [\n `SIMPLELOGIN_ACCESS_TOKEN=${tokens.access_token}; HttpOnly; Secure; SameSite=Lax; Path=/`,\n `SIMPLELOGIN_REFRESH_TOKEN=${tokens.refresh_token}; HttpOnly; Secure; SameSite=Lax; Path=/`,\n ]\n}\n\nfunction clearTokenCookies(): string[] {\n return [\n 'SIMPLELOGIN_ACCESS_TOKEN=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0',\n 'SIMPLELOGIN_REFRESH_TOKEN=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0',\n ]\n}\n\nexport class SimpleLogin {\n private clientId: string\n private clientSecret: string\n private redirectUri: string\n private baseUrl: string\n\n constructor(config: SimpleLoginConfig = {}) {\n this.clientId = config.clientId ?? getEnv('SIMPLELOGIN_CLIENT_ID') ?? ''\n this.clientSecret = config.clientSecret ?? getEnv('SIMPLELOGIN_CLIENT_SECRET') ?? ''\n this.redirectUri = config.redirectUri ?? getEnv('SIMPLELOGIN_REDIRECT_URI') ?? ''\n this.baseUrl = BASE_URL\n\n if (!this.clientId) {\n throw new Error(\n 'clientId is required. Pass it in config or set SIMPLELOGIN_CLIENT_ID env var.',\n )\n }\n if (!this.clientSecret) {\n throw new Error(\n 'clientSecret is required. Pass it in config or set SIMPLELOGIN_CLIENT_SECRET env var.',\n )\n }\n if (!this.redirectUri) {\n throw new Error(\n 'redirectUri is required. Pass it in config or set SIMPLELOGIN_REDIRECT_URI env var.',\n )\n }\n }\n\n /**\n * Returns a Response that redirects to the authorization URL with state and PKCE cookies set.\n * @param options - Optional scopes or custom state\n * @returns A 302 redirect Response ready to be returned from your route handler\n */\n async redirectToAuth(options: AuthorizationUrlOptions = {}): Promise<Response> {\n const { url, cookies } = await this.getAuthorizationUrl(options)\n const headers = new Headers()\n headers.set('Location', url)\n for (const cookie of cookies) {\n headers.append('Set-Cookie', cookie)\n }\n return new Response(null, { status: 302, headers })\n }\n\n /**\n * Generate the authorization URL to redirect users for sign-in\n * @returns The authorization URL, state, code verifier, and ready-to-use Set-Cookie header values\n */\n async getAuthorizationUrl(options: AuthorizationUrlOptions = {}): Promise<AuthorizationUrlResult> {\n const state = options.state ?? generateState()\n const codeVerifier = generateCodeVerifier()\n const codeChallenge = await generateCodeChallenge(codeVerifier)\n\n const params = new URLSearchParams({\n client_id: this.clientId,\n redirect_uri: this.redirectUri,\n response_type: 'code',\n state,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n })\n\n if (options.scopes?.length) {\n params.set('scope', options.scopes.join(' '))\n }\n\n const cookies = [\n `SIMPLELOGIN_STATE=${state}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`,\n `SIMPLELOGIN_CODE_VERIFIER=${codeVerifier}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`,\n ]\n\n return {\n url: `${this.baseUrl}/v1/auth/authorize?${params.toString()}`,\n state,\n codeVerifier,\n cookies,\n }\n }\n\n /**\n * Handle the OAuth callback: verify state, exchange code, fetch user info, and return a redirect Response.\n * @param request - The incoming Request object from the callback\n * @param redirectTo - URL to redirect to after successful authentication (default: '/')\n * @returns CallbackResult with redirect Response (cookies set) and user info to store\n * @throws AuthorizationError if state is missing or doesn't match\n */\n async handleCallback(request: Request, redirectTo: string = '/'): Promise<CallbackResult> {\n const url = new URL(request.url)\n const code = url.searchParams.get('code')\n const state = url.searchParams.get('state')\n\n if (!code) {\n throw new AuthorizationError('Missing authorization code in callback')\n }\n\n if (!state) {\n throw new AuthorizationError('Missing state parameter in callback')\n }\n\n const cookieHeader = request.headers.get('cookie') ?? ''\n const expectedState = parseCookie(cookieHeader, 'SIMPLELOGIN_STATE')\n const codeVerifier = parseCookie(cookieHeader, 'SIMPLELOGIN_CODE_VERIFIER')\n\n if (!expectedState) {\n throw new AuthorizationError('Missing SIMPLELOGIN_STATE cookie')\n }\n\n if (state !== expectedState) {\n throw new AuthorizationError('Invalid state parameter')\n }\n\n if (!codeVerifier) {\n throw new AuthorizationError('Missing SIMPLELOGIN_CODE_VERIFIER cookie')\n }\n\n // Exchange code for tokens (with PKCE code_verifier)\n const tokens = await this.exchangeCode(code, codeVerifier)\n\n // Fetch user info (only time we do this)\n const user = await this.getUserInfo(tokens.access_token)\n\n // Build redirect response with token cookies\n const cookies = createTokenCookies(tokens)\n const headers = new Headers()\n headers.set('Location', redirectTo)\n for (const cookie of cookies) {\n headers.append('Set-Cookie', cookie)\n }\n // Clear the state and code verifier cookies\n headers.append(\n 'Set-Cookie',\n 'SIMPLELOGIN_STATE=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0',\n )\n headers.append(\n 'Set-Cookie',\n 'SIMPLELOGIN_CODE_VERIFIER=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0',\n )\n\n const response = new Response(null, { status: 302, headers })\n\n return { response, user }\n }\n\n /**\n * Exchange an authorization code for access and refresh tokens\n * @param code - The authorization code from the callback\n * @param codeVerifier - The PKCE code verifier\n */\n async exchangeCode(code: string, codeVerifier: string): Promise<TokenResponse> {\n const response = await fetch(`${this.baseUrl}/v1/auth/token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: this.clientId,\n client_secret: this.clientSecret,\n redirect_uri: this.redirectUri,\n code,\n code_verifier: codeVerifier,\n }),\n })\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}))\n throw new TokenError(error.error_description || 'Failed to exchange authorization code')\n }\n\n return response.json()\n }\n\n /**\n * Refresh an access token using a refresh token\n */\n async refreshToken(refreshToken: string): Promise<TokenResponse> {\n const response = await fetch(`${this.baseUrl}/v1/auth/token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: this.clientId,\n client_secret: this.clientSecret,\n refresh_token: refreshToken,\n }),\n })\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}))\n throw new TokenError(error.error_description || 'Failed to refresh token')\n }\n\n return response.json()\n }\n\n /**\n * Get user information using an access token\n */\n async getUserInfo(accessToken: string): Promise<UserInfo> {\n const response = await fetch(`${this.baseUrl}/v1/auth/userinfo`, {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n },\n })\n\n if (!response.ok) {\n throw new AuthorizationError('Failed to fetch user info')\n }\n\n return response.json()\n }\n\n /**\n * Fetch and cache the public key for JWT verification\n */\n private async getPublicKey(): Promise<CryptoKey> {\n const cached = publicKeyCache.get(this.clientId)\n if (cached && Date.now() - cached.fetchedAt < PUBLIC_KEY_CACHE_TTL) {\n return cached.key\n }\n\n const response = await fetch(`${this.baseUrl}/api/applications/${this.clientId}/public-key`)\n if (!response.ok) {\n throw new AuthorizationError('Failed to fetch public key')\n }\n\n const pem = await response.text()\n const key = await importSPKI(pem, 'RS256')\n\n publicKeyCache.set(this.clientId, { key, fetchedAt: Date.now() })\n return key\n }\n\n /**\n * Authenticate a request by verifying the access token from cookies.\n * No network calls except when refreshing expired tokens.\n * @param request - The incoming Request object\n * @param options - Optional settings for CSRF protection\n * @returns AuthResult if authenticated, null if not authenticated\n */\n async authenticate(\n request: Request,\n options?: { allowedOrigin?: string },\n ): Promise<AuthResult | null> {\n // CSRF protection for state-changing requests\n const method = request.method.toUpperCase()\n if (method !== 'GET' && method !== 'HEAD' && options?.allowedOrigin) {\n const origin = request.headers.get('origin') || request.headers.get('referer')\n if (!origin?.startsWith(options.allowedOrigin)) {\n return null\n }\n }\n\n const cookieHeader = request.headers.get('cookie') ?? ''\n const accessToken = parseCookie(cookieHeader, 'SIMPLELOGIN_ACCESS_TOKEN')\n const refreshTokenValue = parseCookie(cookieHeader, 'SIMPLELOGIN_REFRESH_TOKEN')\n\n if (!accessToken) {\n return null\n }\n\n const publicKey = await this.getPublicKey()\n\n try {\n // Verify the token (local, no network call)\n await jwtVerify(accessToken, publicKey)\n\n // Decode claims from JWT (no network call)\n const claims = decodeJwt(accessToken) as AccessTokenClaims\n\n return { claims, accessToken }\n } catch (error) {\n // Check if token is expired\n const isExpired =\n error instanceof Error &&\n 'code' in error &&\n (error as { code: string }).code === 'ERR_JWT_EXPIRED'\n\n if (!isExpired || !refreshTokenValue) {\n return null\n }\n\n // Token expired, try to refresh\n try {\n const tokens = await this.refreshToken(refreshTokenValue)\n const claims = decodeJwt(tokens.access_token) as AccessTokenClaims\n const cookies = createTokenCookies(tokens)\n\n return { claims, accessToken: tokens.access_token, cookies }\n } catch {\n return null\n }\n }\n }\n\n /**\n * Revoke a refresh token on the IdP\n */\n private async revokeToken(refreshToken: string): Promise<void> {\n await fetch(`${this.baseUrl}/v1/auth/revoke`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n token: refreshToken,\n client_id: this.clientId,\n client_secret: this.clientSecret,\n }),\n })\n }\n\n /**\n * Logout: revoke refresh token on IdP and clear cookies.\n * @param request - The incoming Request object (to read refresh token from cookies)\n * @param redirectTo - URL to redirect to after logout (default: '/')\n */\n async logout(request: Request, redirectTo: string = '/'): Promise<Response> {\n const cookieHeader = request.headers.get('cookie') ?? ''\n const refreshToken = parseCookie(cookieHeader, 'SIMPLELOGIN_REFRESH_TOKEN')\n\n // Revoke token on IdP (fire and forget - don't block logout on failure)\n if (refreshToken) {\n this.revokeToken(refreshToken).catch(() => {})\n }\n\n // Clear cookies regardless\n const cookies = clearTokenCookies()\n const headers = new Headers()\n headers.set('Location', redirectTo)\n for (const cookie of cookies) {\n headers.append('Set-Cookie', cookie)\n }\n return new Response(null, { status: 302, headers })\n }\n}\n","export class SousaError extends Error {\n constructor(\n message: string,\n public code: string,\n public statusCode?: number\n ) {\n super(message);\n this.name = \"SousaError\";\n }\n}\n\nexport class AuthorizationError extends SousaError {\n constructor(message: string) {\n super(message, \"AUTHORIZATION_ERROR\", 401);\n this.name = \"AuthorizationError\";\n }\n}\n\nexport class TokenError extends SousaError {\n constructor(message: string) {\n super(message, \"TOKEN_ERROR\", 400);\n this.name = \"TokenError\";\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -10,15 +10,39 @@ interface AuthorizationUrlOptions {
10
10
  interface AuthorizationUrlResult {
11
11
  url: string;
12
12
  state: string;
13
- /** Ready-to-use Set-Cookie header value for the state parameter */
14
- cookie: string;
13
+ codeVerifier: string;
14
+ /** Ready-to-use Set-Cookie header values for state and code verifier */
15
+ cookies: string[];
15
16
  }
16
17
  interface TokenResponse {
17
18
  access_token: string;
18
19
  refresh_token: string;
20
+ token_id: string;
19
21
  token_type: string;
20
22
  expires_in: number;
21
23
  }
24
+ interface AccessTokenClaims {
25
+ sub: string;
26
+ application_id: string;
27
+ organization_id?: string;
28
+ is_master?: boolean;
29
+ type: 'access';
30
+ exp: number;
31
+ iat: number;
32
+ }
33
+ interface AuthResult {
34
+ /** Decoded claims from the access token JWT (no network call) */
35
+ claims: AccessTokenClaims;
36
+ accessToken: string;
37
+ /** Set-Cookie headers to set if tokens were refreshed */
38
+ cookies?: string[];
39
+ }
40
+ interface CallbackResult {
41
+ /** Redirect Response with token cookies set */
42
+ response: Response;
43
+ /** Full user info fetched from userinfo endpoint (store this in your BFF) */
44
+ user: UserInfo;
45
+ }
22
46
  interface UserInfo {
23
47
  sub: string;
24
48
  id: string;
@@ -48,28 +72,30 @@ declare class SimpleLogin {
48
72
  private baseUrl;
49
73
  constructor(config?: SimpleLoginConfig);
50
74
  /**
51
- * Returns a Response that redirects to the authorization URL with the state cookie set.
75
+ * Returns a Response that redirects to the authorization URL with state and PKCE cookies set.
52
76
  * @param options - Optional scopes or custom state
53
77
  * @returns A 302 redirect Response ready to be returned from your route handler
54
78
  */
55
- redirectToAuth(options?: AuthorizationUrlOptions): Response;
79
+ redirectToAuth(options?: AuthorizationUrlOptions): Promise<Response>;
56
80
  /**
57
81
  * Generate the authorization URL to redirect users for sign-in
58
- * @returns The authorization URL, state parameter, and a ready-to-use Set-Cookie header value
82
+ * @returns The authorization URL, state, code verifier, and ready-to-use Set-Cookie header values
59
83
  */
60
- getAuthorizationUrl(options?: AuthorizationUrlOptions): AuthorizationUrlResult;
84
+ getAuthorizationUrl(options?: AuthorizationUrlOptions): Promise<AuthorizationUrlResult>;
61
85
  /**
62
- * Verify the OAuth callback request and exchange the code for tokens.
63
- * Automatically extracts state from SIMPLELOGIN_STATE cookie and code/state from URL.
86
+ * Handle the OAuth callback: verify state, exchange code, fetch user info, and return a redirect Response.
64
87
  * @param request - The incoming Request object from the callback
65
- * @returns The token response if verification succeeds
88
+ * @param redirectTo - URL to redirect to after successful authentication (default: '/')
89
+ * @returns CallbackResult with redirect Response (cookies set) and user info to store
66
90
  * @throws AuthorizationError if state is missing or doesn't match
67
91
  */
68
- handleCallback(request: Request): Promise<TokenResponse>;
92
+ handleCallback(request: Request, redirectTo?: string): Promise<CallbackResult>;
69
93
  /**
70
94
  * Exchange an authorization code for access and refresh tokens
95
+ * @param code - The authorization code from the callback
96
+ * @param codeVerifier - The PKCE code verifier
71
97
  */
72
- exchangeCode(code: string): Promise<TokenResponse>;
98
+ exchangeCode(code: string, codeVerifier: string): Promise<TokenResponse>;
73
99
  /**
74
100
  * Refresh an access token using a refresh token
75
101
  */
@@ -78,6 +104,30 @@ declare class SimpleLogin {
78
104
  * Get user information using an access token
79
105
  */
80
106
  getUserInfo(accessToken: string): Promise<UserInfo>;
107
+ /**
108
+ * Fetch and cache the public key for JWT verification
109
+ */
110
+ private getPublicKey;
111
+ /**
112
+ * Authenticate a request by verifying the access token from cookies.
113
+ * No network calls except when refreshing expired tokens.
114
+ * @param request - The incoming Request object
115
+ * @param options - Optional settings for CSRF protection
116
+ * @returns AuthResult if authenticated, null if not authenticated
117
+ */
118
+ authenticate(request: Request, options?: {
119
+ allowedOrigin?: string;
120
+ }): Promise<AuthResult | null>;
121
+ /**
122
+ * Revoke a refresh token on the IdP
123
+ */
124
+ private revokeToken;
125
+ /**
126
+ * Logout: revoke refresh token on IdP and clear cookies.
127
+ * @param request - The incoming Request object (to read refresh token from cookies)
128
+ * @param redirectTo - URL to redirect to after logout (default: '/')
129
+ */
130
+ logout(request: Request, redirectTo?: string): Promise<Response>;
81
131
  }
82
132
 
83
133
  declare class SousaError extends Error {
@@ -92,4 +142,4 @@ declare class TokenError extends SousaError {
92
142
  constructor(message: string);
93
143
  }
94
144
 
95
- export { AuthorizationError, type AuthorizationUrlOptions, type AuthorizationUrlResult, SimpleLogin, type SimpleLoginConfig, SousaError, TokenError, type TokenResponse, type UserInfo };
145
+ export { type AccessTokenClaims, type AuthResult, AuthorizationError, type AuthorizationUrlOptions, type AuthorizationUrlResult, type CallbackResult, SimpleLogin, type SimpleLoginConfig, SousaError, TokenError, type TokenResponse, type UserInfo };
package/dist/index.d.ts CHANGED
@@ -10,15 +10,39 @@ interface AuthorizationUrlOptions {
10
10
  interface AuthorizationUrlResult {
11
11
  url: string;
12
12
  state: string;
13
- /** Ready-to-use Set-Cookie header value for the state parameter */
14
- cookie: string;
13
+ codeVerifier: string;
14
+ /** Ready-to-use Set-Cookie header values for state and code verifier */
15
+ cookies: string[];
15
16
  }
16
17
  interface TokenResponse {
17
18
  access_token: string;
18
19
  refresh_token: string;
20
+ token_id: string;
19
21
  token_type: string;
20
22
  expires_in: number;
21
23
  }
24
+ interface AccessTokenClaims {
25
+ sub: string;
26
+ application_id: string;
27
+ organization_id?: string;
28
+ is_master?: boolean;
29
+ type: 'access';
30
+ exp: number;
31
+ iat: number;
32
+ }
33
+ interface AuthResult {
34
+ /** Decoded claims from the access token JWT (no network call) */
35
+ claims: AccessTokenClaims;
36
+ accessToken: string;
37
+ /** Set-Cookie headers to set if tokens were refreshed */
38
+ cookies?: string[];
39
+ }
40
+ interface CallbackResult {
41
+ /** Redirect Response with token cookies set */
42
+ response: Response;
43
+ /** Full user info fetched from userinfo endpoint (store this in your BFF) */
44
+ user: UserInfo;
45
+ }
22
46
  interface UserInfo {
23
47
  sub: string;
24
48
  id: string;
@@ -48,28 +72,30 @@ declare class SimpleLogin {
48
72
  private baseUrl;
49
73
  constructor(config?: SimpleLoginConfig);
50
74
  /**
51
- * Returns a Response that redirects to the authorization URL with the state cookie set.
75
+ * Returns a Response that redirects to the authorization URL with state and PKCE cookies set.
52
76
  * @param options - Optional scopes or custom state
53
77
  * @returns A 302 redirect Response ready to be returned from your route handler
54
78
  */
55
- redirectToAuth(options?: AuthorizationUrlOptions): Response;
79
+ redirectToAuth(options?: AuthorizationUrlOptions): Promise<Response>;
56
80
  /**
57
81
  * Generate the authorization URL to redirect users for sign-in
58
- * @returns The authorization URL, state parameter, and a ready-to-use Set-Cookie header value
82
+ * @returns The authorization URL, state, code verifier, and ready-to-use Set-Cookie header values
59
83
  */
60
- getAuthorizationUrl(options?: AuthorizationUrlOptions): AuthorizationUrlResult;
84
+ getAuthorizationUrl(options?: AuthorizationUrlOptions): Promise<AuthorizationUrlResult>;
61
85
  /**
62
- * Verify the OAuth callback request and exchange the code for tokens.
63
- * Automatically extracts state from SIMPLELOGIN_STATE cookie and code/state from URL.
86
+ * Handle the OAuth callback: verify state, exchange code, fetch user info, and return a redirect Response.
64
87
  * @param request - The incoming Request object from the callback
65
- * @returns The token response if verification succeeds
88
+ * @param redirectTo - URL to redirect to after successful authentication (default: '/')
89
+ * @returns CallbackResult with redirect Response (cookies set) and user info to store
66
90
  * @throws AuthorizationError if state is missing or doesn't match
67
91
  */
68
- handleCallback(request: Request): Promise<TokenResponse>;
92
+ handleCallback(request: Request, redirectTo?: string): Promise<CallbackResult>;
69
93
  /**
70
94
  * Exchange an authorization code for access and refresh tokens
95
+ * @param code - The authorization code from the callback
96
+ * @param codeVerifier - The PKCE code verifier
71
97
  */
72
- exchangeCode(code: string): Promise<TokenResponse>;
98
+ exchangeCode(code: string, codeVerifier: string): Promise<TokenResponse>;
73
99
  /**
74
100
  * Refresh an access token using a refresh token
75
101
  */
@@ -78,6 +104,30 @@ declare class SimpleLogin {
78
104
  * Get user information using an access token
79
105
  */
80
106
  getUserInfo(accessToken: string): Promise<UserInfo>;
107
+ /**
108
+ * Fetch and cache the public key for JWT verification
109
+ */
110
+ private getPublicKey;
111
+ /**
112
+ * Authenticate a request by verifying the access token from cookies.
113
+ * No network calls except when refreshing expired tokens.
114
+ * @param request - The incoming Request object
115
+ * @param options - Optional settings for CSRF protection
116
+ * @returns AuthResult if authenticated, null if not authenticated
117
+ */
118
+ authenticate(request: Request, options?: {
119
+ allowedOrigin?: string;
120
+ }): Promise<AuthResult | null>;
121
+ /**
122
+ * Revoke a refresh token on the IdP
123
+ */
124
+ private revokeToken;
125
+ /**
126
+ * Logout: revoke refresh token on IdP and clear cookies.
127
+ * @param request - The incoming Request object (to read refresh token from cookies)
128
+ * @param redirectTo - URL to redirect to after logout (default: '/')
129
+ */
130
+ logout(request: Request, redirectTo?: string): Promise<Response>;
81
131
  }
82
132
 
83
133
  declare class SousaError extends Error {
@@ -92,4 +142,4 @@ declare class TokenError extends SousaError {
92
142
  constructor(message: string);
93
143
  }
94
144
 
95
- export { AuthorizationError, type AuthorizationUrlOptions, type AuthorizationUrlResult, SimpleLogin, type SimpleLoginConfig, SousaError, TokenError, type TokenResponse, type UserInfo };
145
+ export { type AccessTokenClaims, type AuthResult, AuthorizationError, type AuthorizationUrlOptions, type AuthorizationUrlResult, type CallbackResult, SimpleLogin, type SimpleLoginConfig, SousaError, TokenError, type TokenResponse, type UserInfo };
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ // src/client.ts
2
+ import { decodeJwt, importSPKI, jwtVerify } from "jose";
3
+
1
4
  // src/errors.ts
2
5
  var SousaError = class extends Error {
3
6
  constructor(message, code, statusCode) {
@@ -27,6 +30,17 @@ function generateState() {
27
30
  crypto.getRandomValues(array);
28
31
  return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
29
32
  }
33
+ function generateCodeVerifier() {
34
+ const array = new Uint8Array(32);
35
+ crypto.getRandomValues(array);
36
+ return btoa(String.fromCharCode(...array)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
37
+ }
38
+ async function generateCodeChallenge(verifier) {
39
+ const encoder = new TextEncoder();
40
+ const data = encoder.encode(verifier);
41
+ const hash = await crypto.subtle.digest("SHA-256", data);
42
+ return btoa(String.fromCharCode(...new Uint8Array(hash))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
43
+ }
30
44
  function getEnv(key) {
31
45
  if (typeof process !== "undefined" && process.env) {
32
46
  return process.env[key];
@@ -37,6 +51,20 @@ function parseCookie(cookieHeader, name) {
37
51
  const match = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
38
52
  return match ? decodeURIComponent(match[1]) : void 0;
39
53
  }
54
+ var publicKeyCache = /* @__PURE__ */ new Map();
55
+ var PUBLIC_KEY_CACHE_TTL = 60 * 60 * 1e3;
56
+ function createTokenCookies(tokens) {
57
+ return [
58
+ `SIMPLELOGIN_ACCESS_TOKEN=${tokens.access_token}; HttpOnly; Secure; SameSite=Lax; Path=/`,
59
+ `SIMPLELOGIN_REFRESH_TOKEN=${tokens.refresh_token}; HttpOnly; Secure; SameSite=Lax; Path=/`
60
+ ];
61
+ }
62
+ function clearTokenCookies() {
63
+ return [
64
+ "SIMPLELOGIN_ACCESS_TOKEN=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0",
65
+ "SIMPLELOGIN_REFRESH_TOKEN=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0"
66
+ ];
67
+ }
40
68
  var SimpleLogin = class {
41
69
  clientId;
42
70
  clientSecret;
@@ -64,50 +92,57 @@ var SimpleLogin = class {
64
92
  }
65
93
  }
66
94
  /**
67
- * Returns a Response that redirects to the authorization URL with the state cookie set.
95
+ * Returns a Response that redirects to the authorization URL with state and PKCE cookies set.
68
96
  * @param options - Optional scopes or custom state
69
97
  * @returns A 302 redirect Response ready to be returned from your route handler
70
98
  */
71
- redirectToAuth(options = {}) {
72
- const { url, cookie } = this.getAuthorizationUrl(options);
73
- return new Response(null, {
74
- status: 302,
75
- headers: {
76
- Location: url,
77
- "Set-Cookie": cookie
78
- }
79
- });
99
+ async redirectToAuth(options = {}) {
100
+ const { url, cookies } = await this.getAuthorizationUrl(options);
101
+ const headers = new Headers();
102
+ headers.set("Location", url);
103
+ for (const cookie of cookies) {
104
+ headers.append("Set-Cookie", cookie);
105
+ }
106
+ return new Response(null, { status: 302, headers });
80
107
  }
81
108
  /**
82
109
  * Generate the authorization URL to redirect users for sign-in
83
- * @returns The authorization URL, state parameter, and a ready-to-use Set-Cookie header value
110
+ * @returns The authorization URL, state, code verifier, and ready-to-use Set-Cookie header values
84
111
  */
85
- getAuthorizationUrl(options = {}) {
112
+ async getAuthorizationUrl(options = {}) {
86
113
  const state = options.state ?? generateState();
114
+ const codeVerifier = generateCodeVerifier();
115
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
87
116
  const params = new URLSearchParams({
88
117
  client_id: this.clientId,
89
118
  redirect_uri: this.redirectUri,
90
119
  response_type: "code",
91
- state
120
+ state,
121
+ code_challenge: codeChallenge,
122
+ code_challenge_method: "S256"
92
123
  });
93
124
  if (options.scopes?.length) {
94
125
  params.set("scope", options.scopes.join(" "));
95
126
  }
96
- const cookie = `SIMPLELOGIN_STATE=${state}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`;
127
+ const cookies = [
128
+ `SIMPLELOGIN_STATE=${state}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`,
129
+ `SIMPLELOGIN_CODE_VERIFIER=${codeVerifier}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`
130
+ ];
97
131
  return {
98
132
  url: `${this.baseUrl}/v1/auth/authorize?${params.toString()}`,
99
133
  state,
100
- cookie
134
+ codeVerifier,
135
+ cookies
101
136
  };
102
137
  }
103
138
  /**
104
- * Verify the OAuth callback request and exchange the code for tokens.
105
- * Automatically extracts state from SIMPLELOGIN_STATE cookie and code/state from URL.
139
+ * Handle the OAuth callback: verify state, exchange code, fetch user info, and return a redirect Response.
106
140
  * @param request - The incoming Request object from the callback
107
- * @returns The token response if verification succeeds
141
+ * @param redirectTo - URL to redirect to after successful authentication (default: '/')
142
+ * @returns CallbackResult with redirect Response (cookies set) and user info to store
108
143
  * @throws AuthorizationError if state is missing or doesn't match
109
144
  */
110
- async handleCallback(request) {
145
+ async handleCallback(request, redirectTo = "/") {
111
146
  const url = new URL(request.url);
112
147
  const code = url.searchParams.get("code");
113
148
  const state = url.searchParams.get("state");
@@ -119,18 +154,41 @@ var SimpleLogin = class {
119
154
  }
120
155
  const cookieHeader = request.headers.get("cookie") ?? "";
121
156
  const expectedState = parseCookie(cookieHeader, "SIMPLELOGIN_STATE");
157
+ const codeVerifier = parseCookie(cookieHeader, "SIMPLELOGIN_CODE_VERIFIER");
122
158
  if (!expectedState) {
123
159
  throw new AuthorizationError("Missing SIMPLELOGIN_STATE cookie");
124
160
  }
125
161
  if (state !== expectedState) {
126
162
  throw new AuthorizationError("Invalid state parameter");
127
163
  }
128
- return this.exchangeCode(code);
164
+ if (!codeVerifier) {
165
+ throw new AuthorizationError("Missing SIMPLELOGIN_CODE_VERIFIER cookie");
166
+ }
167
+ const tokens = await this.exchangeCode(code, codeVerifier);
168
+ const user = await this.getUserInfo(tokens.access_token);
169
+ const cookies = createTokenCookies(tokens);
170
+ const headers = new Headers();
171
+ headers.set("Location", redirectTo);
172
+ for (const cookie of cookies) {
173
+ headers.append("Set-Cookie", cookie);
174
+ }
175
+ headers.append(
176
+ "Set-Cookie",
177
+ "SIMPLELOGIN_STATE=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0"
178
+ );
179
+ headers.append(
180
+ "Set-Cookie",
181
+ "SIMPLELOGIN_CODE_VERIFIER=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0"
182
+ );
183
+ const response = new Response(null, { status: 302, headers });
184
+ return { response, user };
129
185
  }
130
186
  /**
131
187
  * Exchange an authorization code for access and refresh tokens
188
+ * @param code - The authorization code from the callback
189
+ * @param codeVerifier - The PKCE code verifier
132
190
  */
133
- async exchangeCode(code) {
191
+ async exchangeCode(code, codeVerifier) {
134
192
  const response = await fetch(`${this.baseUrl}/v1/auth/token`, {
135
193
  method: "POST",
136
194
  headers: {
@@ -141,7 +199,8 @@ var SimpleLogin = class {
141
199
  client_id: this.clientId,
142
200
  client_secret: this.clientSecret,
143
201
  redirect_uri: this.redirectUri,
144
- code
202
+ code,
203
+ code_verifier: codeVerifier
145
204
  })
146
205
  });
147
206
  if (!response.ok) {
@@ -186,6 +245,98 @@ var SimpleLogin = class {
186
245
  }
187
246
  return response.json();
188
247
  }
248
+ /**
249
+ * Fetch and cache the public key for JWT verification
250
+ */
251
+ async getPublicKey() {
252
+ const cached = publicKeyCache.get(this.clientId);
253
+ if (cached && Date.now() - cached.fetchedAt < PUBLIC_KEY_CACHE_TTL) {
254
+ return cached.key;
255
+ }
256
+ const response = await fetch(`${this.baseUrl}/api/applications/${this.clientId}/public-key`);
257
+ if (!response.ok) {
258
+ throw new AuthorizationError("Failed to fetch public key");
259
+ }
260
+ const pem = await response.text();
261
+ const key = await importSPKI(pem, "RS256");
262
+ publicKeyCache.set(this.clientId, { key, fetchedAt: Date.now() });
263
+ return key;
264
+ }
265
+ /**
266
+ * Authenticate a request by verifying the access token from cookies.
267
+ * No network calls except when refreshing expired tokens.
268
+ * @param request - The incoming Request object
269
+ * @param options - Optional settings for CSRF protection
270
+ * @returns AuthResult if authenticated, null if not authenticated
271
+ */
272
+ async authenticate(request, options) {
273
+ const method = request.method.toUpperCase();
274
+ if (method !== "GET" && method !== "HEAD" && options?.allowedOrigin) {
275
+ const origin = request.headers.get("origin") || request.headers.get("referer");
276
+ if (!origin?.startsWith(options.allowedOrigin)) {
277
+ return null;
278
+ }
279
+ }
280
+ const cookieHeader = request.headers.get("cookie") ?? "";
281
+ const accessToken = parseCookie(cookieHeader, "SIMPLELOGIN_ACCESS_TOKEN");
282
+ const refreshTokenValue = parseCookie(cookieHeader, "SIMPLELOGIN_REFRESH_TOKEN");
283
+ if (!accessToken) {
284
+ return null;
285
+ }
286
+ const publicKey = await this.getPublicKey();
287
+ try {
288
+ await jwtVerify(accessToken, publicKey);
289
+ const claims = decodeJwt(accessToken);
290
+ return { claims, accessToken };
291
+ } catch (error) {
292
+ const isExpired = error instanceof Error && "code" in error && error.code === "ERR_JWT_EXPIRED";
293
+ if (!isExpired || !refreshTokenValue) {
294
+ return null;
295
+ }
296
+ try {
297
+ const tokens = await this.refreshToken(refreshTokenValue);
298
+ const claims = decodeJwt(tokens.access_token);
299
+ const cookies = createTokenCookies(tokens);
300
+ return { claims, accessToken: tokens.access_token, cookies };
301
+ } catch {
302
+ return null;
303
+ }
304
+ }
305
+ }
306
+ /**
307
+ * Revoke a refresh token on the IdP
308
+ */
309
+ async revokeToken(refreshToken) {
310
+ await fetch(`${this.baseUrl}/v1/auth/revoke`, {
311
+ method: "POST",
312
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
313
+ body: new URLSearchParams({
314
+ token: refreshToken,
315
+ client_id: this.clientId,
316
+ client_secret: this.clientSecret
317
+ })
318
+ });
319
+ }
320
+ /**
321
+ * Logout: revoke refresh token on IdP and clear cookies.
322
+ * @param request - The incoming Request object (to read refresh token from cookies)
323
+ * @param redirectTo - URL to redirect to after logout (default: '/')
324
+ */
325
+ async logout(request, redirectTo = "/") {
326
+ const cookieHeader = request.headers.get("cookie") ?? "";
327
+ const refreshToken = parseCookie(cookieHeader, "SIMPLELOGIN_REFRESH_TOKEN");
328
+ if (refreshToken) {
329
+ this.revokeToken(refreshToken).catch(() => {
330
+ });
331
+ }
332
+ const cookies = clearTokenCookies();
333
+ const headers = new Headers();
334
+ headers.set("Location", redirectTo);
335
+ for (const cookie of cookies) {
336
+ headers.append("Set-Cookie", cookie);
337
+ }
338
+ return new Response(null, { status: 302, headers });
339
+ }
189
340
  };
190
341
  export {
191
342
  AuthorizationError,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/errors.ts","../src/client.ts"],"sourcesContent":["export class SousaError extends Error {\n constructor(\n message: string,\n public code: string,\n public statusCode?: number\n ) {\n super(message);\n this.name = \"SousaError\";\n }\n}\n\nexport class AuthorizationError extends SousaError {\n constructor(message: string) {\n super(message, \"AUTHORIZATION_ERROR\", 401);\n this.name = \"AuthorizationError\";\n }\n}\n\nexport class TokenError extends SousaError {\n constructor(message: string) {\n super(message, \"TOKEN_ERROR\", 400);\n this.name = \"TokenError\";\n }\n}\n","import { AuthorizationError, TokenError } from './errors'\nimport type {\n AuthorizationUrlOptions,\n AuthorizationUrlResult,\n SimpleLoginConfig,\n TokenResponse,\n UserInfo,\n} from './types'\n\ndeclare const __SIMPLELOGIN_BASE_URL__: string\n\nconst BASE_URL = __SIMPLELOGIN_BASE_URL__\n\nfunction generateState(): string {\n const array = new Uint8Array(32)\n crypto.getRandomValues(array)\n return Array.from(array, (b) => b.toString(16).padStart(2, '0')).join('')\n}\n\nfunction getEnv(key: string): string | undefined {\n if (typeof process !== 'undefined' && process.env) {\n return process.env[key]\n }\n return undefined\n}\n\nfunction parseCookie(cookieHeader: string, name: string): string | undefined {\n const match = cookieHeader.match(new RegExp(`(?:^|;\\\\s*)${name}=([^;]*)`))\n return match ? decodeURIComponent(match[1]) : undefined\n}\n\nexport class SimpleLogin {\n private clientId: string\n private clientSecret: string\n private redirectUri: string\n private baseUrl: string\n\n constructor(config: SimpleLoginConfig = {}) {\n this.clientId = config.clientId ?? getEnv('SIMPLELOGIN_CLIENT_ID') ?? ''\n this.clientSecret = config.clientSecret ?? getEnv('SIMPLELOGIN_CLIENT_SECRET') ?? ''\n this.redirectUri = config.redirectUri ?? getEnv('SIMPLELOGIN_REDIRECT_URI') ?? ''\n this.baseUrl = BASE_URL\n\n if (!this.clientId) {\n throw new Error(\n 'clientId is required. Pass it in config or set SIMPLELOGIN_CLIENT_ID env var.',\n )\n }\n if (!this.clientSecret) {\n throw new Error(\n 'clientSecret is required. Pass it in config or set SIMPLELOGIN_CLIENT_SECRET env var.',\n )\n }\n if (!this.redirectUri) {\n throw new Error(\n 'redirectUri is required. Pass it in config or set SIMPLELOGIN_REDIRECT_URI env var.',\n )\n }\n }\n\n /**\n * Returns a Response that redirects to the authorization URL with the state cookie set.\n * @param options - Optional scopes or custom state\n * @returns A 302 redirect Response ready to be returned from your route handler\n */\n redirectToAuth(options: AuthorizationUrlOptions = {}): Response {\n const { url, cookie } = this.getAuthorizationUrl(options)\n return new Response(null, {\n status: 302,\n headers: {\n Location: url,\n 'Set-Cookie': cookie,\n },\n })\n }\n\n /**\n * Generate the authorization URL to redirect users for sign-in\n * @returns The authorization URL, state parameter, and a ready-to-use Set-Cookie header value\n */\n getAuthorizationUrl(options: AuthorizationUrlOptions = {}): AuthorizationUrlResult {\n const state = options.state ?? generateState()\n\n const params = new URLSearchParams({\n client_id: this.clientId,\n redirect_uri: this.redirectUri,\n response_type: 'code',\n state,\n })\n\n if (options.scopes?.length) {\n params.set('scope', options.scopes.join(' '))\n }\n\n const cookie = `SIMPLELOGIN_STATE=${state}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`\n\n return {\n url: `${this.baseUrl}/v1/auth/authorize?${params.toString()}`,\n state,\n cookie,\n }\n }\n\n /**\n * Verify the OAuth callback request and exchange the code for tokens.\n * Automatically extracts state from SIMPLELOGIN_STATE cookie and code/state from URL.\n * @param request - The incoming Request object from the callback\n * @returns The token response if verification succeeds\n * @throws AuthorizationError if state is missing or doesn't match\n */\n async handleCallback(request: Request): Promise<TokenResponse> {\n const url = new URL(request.url)\n const code = url.searchParams.get('code')\n const state = url.searchParams.get('state')\n\n if (!code) {\n throw new AuthorizationError('Missing authorization code in callback')\n }\n\n if (!state) {\n throw new AuthorizationError('Missing state parameter in callback')\n }\n\n const cookieHeader = request.headers.get('cookie') ?? ''\n const expectedState = parseCookie(cookieHeader, 'SIMPLELOGIN_STATE')\n\n if (!expectedState) {\n throw new AuthorizationError('Missing SIMPLELOGIN_STATE cookie')\n }\n\n if (state !== expectedState) {\n throw new AuthorizationError('Invalid state parameter')\n }\n\n return this.exchangeCode(code)\n }\n\n /**\n * Exchange an authorization code for access and refresh tokens\n */\n async exchangeCode(code: string): Promise<TokenResponse> {\n const response = await fetch(`${this.baseUrl}/v1/auth/token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: this.clientId,\n client_secret: this.clientSecret,\n redirect_uri: this.redirectUri,\n code,\n }),\n })\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}))\n throw new TokenError(error.error_description || 'Failed to exchange authorization code')\n }\n\n return response.json()\n }\n\n /**\n * Refresh an access token using a refresh token\n */\n async refreshToken(refreshToken: string): Promise<TokenResponse> {\n const response = await fetch(`${this.baseUrl}/v1/auth/token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: this.clientId,\n client_secret: this.clientSecret,\n refresh_token: refreshToken,\n }),\n })\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}))\n throw new TokenError(error.error_description || 'Failed to refresh token')\n }\n\n return response.json()\n }\n\n /**\n * Get user information using an access token\n */\n async getUserInfo(accessToken: string): Promise<UserInfo> {\n const response = await fetch(`${this.baseUrl}/v1/auth/userinfo`, {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n },\n })\n\n if (!response.ok) {\n throw new AuthorizationError('Failed to fetch user info')\n }\n\n return response.json()\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/client.ts","../src/errors.ts"],"sourcesContent":["import { decodeJwt, importSPKI, jwtVerify } from 'jose'\nimport { AuthorizationError, TokenError } from './errors'\nimport type {\n AccessTokenClaims,\n AuthResult,\n AuthorizationUrlOptions,\n AuthorizationUrlResult,\n CallbackResult,\n SimpleLoginConfig,\n TokenResponse,\n UserInfo,\n} from './types'\n\ndeclare const __SIMPLELOGIN_BASE_URL__: string\n\nconst BASE_URL = __SIMPLELOGIN_BASE_URL__\n\nfunction generateState(): string {\n const array = new Uint8Array(32)\n crypto.getRandomValues(array)\n return Array.from(array, (b) => b.toString(16).padStart(2, '0')).join('')\n}\n\nfunction generateCodeVerifier(): string {\n const array = new Uint8Array(32)\n crypto.getRandomValues(array)\n // Base64url encode (RFC 7636)\n return btoa(String.fromCharCode(...array))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '')\n}\n\nasync function generateCodeChallenge(verifier: string): Promise<string> {\n const encoder = new TextEncoder()\n const data = encoder.encode(verifier)\n const hash = await crypto.subtle.digest('SHA-256', data)\n // Base64url encode the hash\n return btoa(String.fromCharCode(...new Uint8Array(hash)))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=/g, '')\n}\n\nfunction getEnv(key: string): string | undefined {\n if (typeof process !== 'undefined' && process.env) {\n return process.env[key]\n }\n return undefined\n}\n\nfunction parseCookie(cookieHeader: string, name: string): string | undefined {\n const match = cookieHeader.match(new RegExp(`(?:^|;\\\\s*)${name}=([^;]*)`))\n return match ? decodeURIComponent(match[1]) : undefined\n}\n\n// Public key cache: clientId -> { key, fetchedAt }\nconst publicKeyCache = new Map<string, { key: CryptoKey; fetchedAt: number }>()\nconst PUBLIC_KEY_CACHE_TTL = 60 * 60 * 1000 // 1 hour\n\nfunction createTokenCookies(tokens: TokenResponse): string[] {\n return [\n `SIMPLELOGIN_ACCESS_TOKEN=${tokens.access_token}; HttpOnly; Secure; SameSite=Lax; Path=/`,\n `SIMPLELOGIN_REFRESH_TOKEN=${tokens.refresh_token}; HttpOnly; Secure; SameSite=Lax; Path=/`,\n ]\n}\n\nfunction clearTokenCookies(): string[] {\n return [\n 'SIMPLELOGIN_ACCESS_TOKEN=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0',\n 'SIMPLELOGIN_REFRESH_TOKEN=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0',\n ]\n}\n\nexport class SimpleLogin {\n private clientId: string\n private clientSecret: string\n private redirectUri: string\n private baseUrl: string\n\n constructor(config: SimpleLoginConfig = {}) {\n this.clientId = config.clientId ?? getEnv('SIMPLELOGIN_CLIENT_ID') ?? ''\n this.clientSecret = config.clientSecret ?? getEnv('SIMPLELOGIN_CLIENT_SECRET') ?? ''\n this.redirectUri = config.redirectUri ?? getEnv('SIMPLELOGIN_REDIRECT_URI') ?? ''\n this.baseUrl = BASE_URL\n\n if (!this.clientId) {\n throw new Error(\n 'clientId is required. Pass it in config or set SIMPLELOGIN_CLIENT_ID env var.',\n )\n }\n if (!this.clientSecret) {\n throw new Error(\n 'clientSecret is required. Pass it in config or set SIMPLELOGIN_CLIENT_SECRET env var.',\n )\n }\n if (!this.redirectUri) {\n throw new Error(\n 'redirectUri is required. Pass it in config or set SIMPLELOGIN_REDIRECT_URI env var.',\n )\n }\n }\n\n /**\n * Returns a Response that redirects to the authorization URL with state and PKCE cookies set.\n * @param options - Optional scopes or custom state\n * @returns A 302 redirect Response ready to be returned from your route handler\n */\n async redirectToAuth(options: AuthorizationUrlOptions = {}): Promise<Response> {\n const { url, cookies } = await this.getAuthorizationUrl(options)\n const headers = new Headers()\n headers.set('Location', url)\n for (const cookie of cookies) {\n headers.append('Set-Cookie', cookie)\n }\n return new Response(null, { status: 302, headers })\n }\n\n /**\n * Generate the authorization URL to redirect users for sign-in\n * @returns The authorization URL, state, code verifier, and ready-to-use Set-Cookie header values\n */\n async getAuthorizationUrl(options: AuthorizationUrlOptions = {}): Promise<AuthorizationUrlResult> {\n const state = options.state ?? generateState()\n const codeVerifier = generateCodeVerifier()\n const codeChallenge = await generateCodeChallenge(codeVerifier)\n\n const params = new URLSearchParams({\n client_id: this.clientId,\n redirect_uri: this.redirectUri,\n response_type: 'code',\n state,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n })\n\n if (options.scopes?.length) {\n params.set('scope', options.scopes.join(' '))\n }\n\n const cookies = [\n `SIMPLELOGIN_STATE=${state}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`,\n `SIMPLELOGIN_CODE_VERIFIER=${codeVerifier}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`,\n ]\n\n return {\n url: `${this.baseUrl}/v1/auth/authorize?${params.toString()}`,\n state,\n codeVerifier,\n cookies,\n }\n }\n\n /**\n * Handle the OAuth callback: verify state, exchange code, fetch user info, and return a redirect Response.\n * @param request - The incoming Request object from the callback\n * @param redirectTo - URL to redirect to after successful authentication (default: '/')\n * @returns CallbackResult with redirect Response (cookies set) and user info to store\n * @throws AuthorizationError if state is missing or doesn't match\n */\n async handleCallback(request: Request, redirectTo: string = '/'): Promise<CallbackResult> {\n const url = new URL(request.url)\n const code = url.searchParams.get('code')\n const state = url.searchParams.get('state')\n\n if (!code) {\n throw new AuthorizationError('Missing authorization code in callback')\n }\n\n if (!state) {\n throw new AuthorizationError('Missing state parameter in callback')\n }\n\n const cookieHeader = request.headers.get('cookie') ?? ''\n const expectedState = parseCookie(cookieHeader, 'SIMPLELOGIN_STATE')\n const codeVerifier = parseCookie(cookieHeader, 'SIMPLELOGIN_CODE_VERIFIER')\n\n if (!expectedState) {\n throw new AuthorizationError('Missing SIMPLELOGIN_STATE cookie')\n }\n\n if (state !== expectedState) {\n throw new AuthorizationError('Invalid state parameter')\n }\n\n if (!codeVerifier) {\n throw new AuthorizationError('Missing SIMPLELOGIN_CODE_VERIFIER cookie')\n }\n\n // Exchange code for tokens (with PKCE code_verifier)\n const tokens = await this.exchangeCode(code, codeVerifier)\n\n // Fetch user info (only time we do this)\n const user = await this.getUserInfo(tokens.access_token)\n\n // Build redirect response with token cookies\n const cookies = createTokenCookies(tokens)\n const headers = new Headers()\n headers.set('Location', redirectTo)\n for (const cookie of cookies) {\n headers.append('Set-Cookie', cookie)\n }\n // Clear the state and code verifier cookies\n headers.append(\n 'Set-Cookie',\n 'SIMPLELOGIN_STATE=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0',\n )\n headers.append(\n 'Set-Cookie',\n 'SIMPLELOGIN_CODE_VERIFIER=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0',\n )\n\n const response = new Response(null, { status: 302, headers })\n\n return { response, user }\n }\n\n /**\n * Exchange an authorization code for access and refresh tokens\n * @param code - The authorization code from the callback\n * @param codeVerifier - The PKCE code verifier\n */\n async exchangeCode(code: string, codeVerifier: string): Promise<TokenResponse> {\n const response = await fetch(`${this.baseUrl}/v1/auth/token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n client_id: this.clientId,\n client_secret: this.clientSecret,\n redirect_uri: this.redirectUri,\n code,\n code_verifier: codeVerifier,\n }),\n })\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}))\n throw new TokenError(error.error_description || 'Failed to exchange authorization code')\n }\n\n return response.json()\n }\n\n /**\n * Refresh an access token using a refresh token\n */\n async refreshToken(refreshToken: string): Promise<TokenResponse> {\n const response = await fetch(`${this.baseUrl}/v1/auth/token`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n client_id: this.clientId,\n client_secret: this.clientSecret,\n refresh_token: refreshToken,\n }),\n })\n\n if (!response.ok) {\n const error = await response.json().catch(() => ({}))\n throw new TokenError(error.error_description || 'Failed to refresh token')\n }\n\n return response.json()\n }\n\n /**\n * Get user information using an access token\n */\n async getUserInfo(accessToken: string): Promise<UserInfo> {\n const response = await fetch(`${this.baseUrl}/v1/auth/userinfo`, {\n headers: {\n Authorization: `Bearer ${accessToken}`,\n },\n })\n\n if (!response.ok) {\n throw new AuthorizationError('Failed to fetch user info')\n }\n\n return response.json()\n }\n\n /**\n * Fetch and cache the public key for JWT verification\n */\n private async getPublicKey(): Promise<CryptoKey> {\n const cached = publicKeyCache.get(this.clientId)\n if (cached && Date.now() - cached.fetchedAt < PUBLIC_KEY_CACHE_TTL) {\n return cached.key\n }\n\n const response = await fetch(`${this.baseUrl}/api/applications/${this.clientId}/public-key`)\n if (!response.ok) {\n throw new AuthorizationError('Failed to fetch public key')\n }\n\n const pem = await response.text()\n const key = await importSPKI(pem, 'RS256')\n\n publicKeyCache.set(this.clientId, { key, fetchedAt: Date.now() })\n return key\n }\n\n /**\n * Authenticate a request by verifying the access token from cookies.\n * No network calls except when refreshing expired tokens.\n * @param request - The incoming Request object\n * @param options - Optional settings for CSRF protection\n * @returns AuthResult if authenticated, null if not authenticated\n */\n async authenticate(\n request: Request,\n options?: { allowedOrigin?: string },\n ): Promise<AuthResult | null> {\n // CSRF protection for state-changing requests\n const method = request.method.toUpperCase()\n if (method !== 'GET' && method !== 'HEAD' && options?.allowedOrigin) {\n const origin = request.headers.get('origin') || request.headers.get('referer')\n if (!origin?.startsWith(options.allowedOrigin)) {\n return null\n }\n }\n\n const cookieHeader = request.headers.get('cookie') ?? ''\n const accessToken = parseCookie(cookieHeader, 'SIMPLELOGIN_ACCESS_TOKEN')\n const refreshTokenValue = parseCookie(cookieHeader, 'SIMPLELOGIN_REFRESH_TOKEN')\n\n if (!accessToken) {\n return null\n }\n\n const publicKey = await this.getPublicKey()\n\n try {\n // Verify the token (local, no network call)\n await jwtVerify(accessToken, publicKey)\n\n // Decode claims from JWT (no network call)\n const claims = decodeJwt(accessToken) as AccessTokenClaims\n\n return { claims, accessToken }\n } catch (error) {\n // Check if token is expired\n const isExpired =\n error instanceof Error &&\n 'code' in error &&\n (error as { code: string }).code === 'ERR_JWT_EXPIRED'\n\n if (!isExpired || !refreshTokenValue) {\n return null\n }\n\n // Token expired, try to refresh\n try {\n const tokens = await this.refreshToken(refreshTokenValue)\n const claims = decodeJwt(tokens.access_token) as AccessTokenClaims\n const cookies = createTokenCookies(tokens)\n\n return { claims, accessToken: tokens.access_token, cookies }\n } catch {\n return null\n }\n }\n }\n\n /**\n * Revoke a refresh token on the IdP\n */\n private async revokeToken(refreshToken: string): Promise<void> {\n await fetch(`${this.baseUrl}/v1/auth/revoke`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({\n token: refreshToken,\n client_id: this.clientId,\n client_secret: this.clientSecret,\n }),\n })\n }\n\n /**\n * Logout: revoke refresh token on IdP and clear cookies.\n * @param request - The incoming Request object (to read refresh token from cookies)\n * @param redirectTo - URL to redirect to after logout (default: '/')\n */\n async logout(request: Request, redirectTo: string = '/'): Promise<Response> {\n const cookieHeader = request.headers.get('cookie') ?? ''\n const refreshToken = parseCookie(cookieHeader, 'SIMPLELOGIN_REFRESH_TOKEN')\n\n // Revoke token on IdP (fire and forget - don't block logout on failure)\n if (refreshToken) {\n this.revokeToken(refreshToken).catch(() => {})\n }\n\n // Clear cookies regardless\n const cookies = clearTokenCookies()\n const headers = new Headers()\n headers.set('Location', redirectTo)\n for (const cookie of cookies) {\n headers.append('Set-Cookie', cookie)\n }\n return new Response(null, { status: 302, headers })\n }\n}\n","export class SousaError extends Error {\n constructor(\n message: string,\n public code: string,\n public statusCode?: number\n ) {\n super(message);\n this.name = \"SousaError\";\n }\n}\n\nexport class AuthorizationError extends SousaError {\n constructor(message: string) {\n super(message, \"AUTHORIZATION_ERROR\", 401);\n this.name = \"AuthorizationError\";\n }\n}\n\nexport class TokenError extends SousaError {\n constructor(message: string) {\n super(message, \"TOKEN_ERROR\", 400);\n this.name = \"TokenError\";\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simple-login/sdk",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Official SDK for Simple Login",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -42,5 +42,8 @@
42
42
  },
43
43
  "engines": {
44
44
  "node": ">=18.0.0"
45
+ },
46
+ "dependencies": {
47
+ "jose": "^6.1.3"
45
48
  }
46
49
  }