@simple-login/sdk 0.1.4 → 1.1.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) {
@@ -51,12 +54,35 @@ var TokenError = class extends SousaError {
51
54
 
52
55
  // src/client.ts
53
56
  var BASE_URL = "https://app.simple-login.com";
57
+ function generateState() {
58
+ const array = new Uint8Array(32);
59
+ crypto.getRandomValues(array);
60
+ return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
61
+ }
54
62
  function getEnv(key) {
55
63
  if (typeof process !== "undefined" && process.env) {
56
64
  return process.env[key];
57
65
  }
58
66
  return void 0;
59
67
  }
68
+ function parseCookie(cookieHeader, name) {
69
+ const match = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
70
+ return match ? decodeURIComponent(match[1]) : void 0;
71
+ }
72
+ var publicKeyCache = /* @__PURE__ */ new Map();
73
+ var PUBLIC_KEY_CACHE_TTL = 60 * 60 * 1e3;
74
+ function createTokenCookies(tokens) {
75
+ return [
76
+ `SIMPLELOGIN_ACCESS_TOKEN=${tokens.access_token}; HttpOnly; Secure; SameSite=Lax; Path=/`,
77
+ `SIMPLELOGIN_REFRESH_TOKEN=${tokens.refresh_token}; HttpOnly; Secure; SameSite=Lax; Path=/`
78
+ ];
79
+ }
80
+ function clearTokenCookies() {
81
+ return [
82
+ "SIMPLELOGIN_ACCESS_TOKEN=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0",
83
+ "SIMPLELOGIN_REFRESH_TOKEN=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0"
84
+ ];
85
+ }
60
86
  var SimpleLogin = class {
61
87
  clientId;
62
88
  clientSecret;
@@ -83,22 +109,82 @@ var SimpleLogin = class {
83
109
  );
84
110
  }
85
111
  }
112
+ /**
113
+ * Returns a Response that redirects to the authorization URL with the state cookie set.
114
+ * @param options - Optional scopes or custom state
115
+ * @returns A 302 redirect Response ready to be returned from your route handler
116
+ */
117
+ redirectToAuth(options = {}) {
118
+ const { url, cookie } = this.getAuthorizationUrl(options);
119
+ return new Response(null, {
120
+ status: 302,
121
+ headers: {
122
+ Location: url,
123
+ "Set-Cookie": cookie
124
+ }
125
+ });
126
+ }
86
127
  /**
87
128
  * Generate the authorization URL to redirect users for sign-in
129
+ * @returns The authorization URL, state parameter, and a ready-to-use Set-Cookie header value
88
130
  */
89
131
  getAuthorizationUrl(options = {}) {
132
+ const state = options.state ?? generateState();
90
133
  const params = new URLSearchParams({
91
134
  client_id: this.clientId,
92
135
  redirect_uri: this.redirectUri,
93
- response_type: "code"
136
+ response_type: "code",
137
+ state
94
138
  });
95
- if (options.state) {
96
- params.set("state", options.state);
97
- }
98
139
  if (options.scopes?.length) {
99
140
  params.set("scope", options.scopes.join(" "));
100
141
  }
101
- return `${this.baseUrl}/v1/auth/authorize?${params.toString()}`;
142
+ const cookie = `SIMPLELOGIN_STATE=${state}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`;
143
+ return {
144
+ url: `${this.baseUrl}/v1/auth/authorize?${params.toString()}`,
145
+ state,
146
+ cookie
147
+ };
148
+ }
149
+ /**
150
+ * Handle the OAuth callback: verify state, exchange code, fetch user info, and return a redirect Response.
151
+ * @param request - The incoming Request object from the callback
152
+ * @param redirectTo - URL to redirect to after successful authentication (default: '/')
153
+ * @returns CallbackResult with redirect Response (cookies set) and user info to store
154
+ * @throws AuthorizationError if state is missing or doesn't match
155
+ */
156
+ async handleCallback(request, redirectTo = "/") {
157
+ const url = new URL(request.url);
158
+ const code = url.searchParams.get("code");
159
+ const state = url.searchParams.get("state");
160
+ if (!code) {
161
+ throw new AuthorizationError("Missing authorization code in callback");
162
+ }
163
+ if (!state) {
164
+ throw new AuthorizationError("Missing state parameter in callback");
165
+ }
166
+ const cookieHeader = request.headers.get("cookie") ?? "";
167
+ const expectedState = parseCookie(cookieHeader, "SIMPLELOGIN_STATE");
168
+ if (!expectedState) {
169
+ throw new AuthorizationError("Missing SIMPLELOGIN_STATE cookie");
170
+ }
171
+ if (state !== expectedState) {
172
+ throw new AuthorizationError("Invalid state parameter");
173
+ }
174
+ const tokens = await this.exchangeCode(code);
175
+ const user = await this.getUserInfo(tokens.access_token);
176
+ const cookies = createTokenCookies(tokens);
177
+ const headers = new Headers();
178
+ headers.set("Location", redirectTo);
179
+ for (const cookie of cookies) {
180
+ headers.append("Set-Cookie", cookie);
181
+ }
182
+ headers.append(
183
+ "Set-Cookie",
184
+ "SIMPLELOGIN_STATE=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0"
185
+ );
186
+ const response = new Response(null, { status: 302, headers });
187
+ return { response, user };
102
188
  }
103
189
  /**
104
190
  * Exchange an authorization code for access and refresh tokens
@@ -159,6 +245,98 @@ var SimpleLogin = class {
159
245
  }
160
246
  return response.json();
161
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 (0, import_jose.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 (0, import_jose.jwtVerify)(accessToken, publicKey);
289
+ const claims = (0, import_jose.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 = (0, import_jose.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
+ }
162
340
  };
163
341
  // Annotate the CommonJS export names for ESM import in node:
164
342
  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 { AuthorizationUrlOptions, SimpleLoginConfig, TokenResponse, UserInfo } 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 { AuthorizationUrlOptions, SimpleLoginConfig, TokenResponse, UserInfo } from './types'\n\ndeclare const __SIMPLELOGIN_BASE_URL__: string\n\nconst BASE_URL = __SIMPLELOGIN_BASE_URL__\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\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 * Generate the authorization URL to redirect users for sign-in\n */\n getAuthorizationUrl(options: AuthorizationUrlOptions = {}): string {\n const params = new URLSearchParams({\n client_id: this.clientId,\n redirect_uri: this.redirectUri,\n response_type: 'code',\n })\n\n if (options.state) {\n params.set('state', options.state)\n }\n\n if (options.scopes?.length) {\n params.set('scope', options.scopes.join(' '))\n }\n\n return `${this.baseUrl}/v1/auth/authorize?${params.toString()}`\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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,IAAM,aAAN,cAAyB,MAAM;AAAA,EACpC,YACE,SACO,MACA,YACP;AACA,UAAM,OAAO;AAHN;AACA;AAGP,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,qBAAN,cAAiC,WAAW;AAAA,EACjD,YAAY,SAAiB;AAC3B,UAAM,SAAS,uBAAuB,GAAG;AACzC,SAAK,OAAO;AAAA,EACd;AACF;AAEO,IAAM,aAAN,cAAyB,WAAW;AAAA,EACzC,YAAY,SAAiB;AAC3B,UAAM,SAAS,eAAe,GAAG;AACjC,SAAK,OAAO;AAAA,EACd;AACF;;;AClBA,IAAM,WAAW;AAEjB,SAAS,OAAO,KAAiC;AAC/C,MAAI,OAAO,YAAY,eAAe,QAAQ,KAAK;AACjD,WAAO,QAAQ,IAAI,GAAG;AAAA,EACxB;AACA,SAAO;AACT;AAEO,IAAM,cAAN,MAAkB;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,SAA4B,CAAC,GAAG;AAC1C,SAAK,WAAW,OAAO,YAAY,OAAO,uBAAuB,KAAK;AACtE,SAAK,eAAe,OAAO,gBAAgB,OAAO,2BAA2B,KAAK;AAClF,SAAK,cAAc,OAAO,eAAe,OAAO,0BAA0B,KAAK;AAC/E,SAAK,UAAU;AAEf,QAAI,CAAC,KAAK,UAAU;AAClB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,QAAI,CAAC,KAAK,cAAc;AACtB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,QAAI,CAAC,KAAK,aAAa;AACrB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAoB,UAAmC,CAAC,GAAW;AACjE,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,KAAK;AAAA,MAChB,cAAc,KAAK;AAAA,MACnB,eAAe;AAAA,IACjB,CAAC;AAED,QAAI,QAAQ,OAAO;AACjB,aAAO,IAAI,SAAS,QAAQ,KAAK;AAAA,IACnC;AAEA,QAAI,QAAQ,QAAQ,QAAQ;AAC1B,aAAO,IAAI,SAAS,QAAQ,OAAO,KAAK,GAAG,CAAC;AAAA,IAC9C;AAEA,WAAO,GAAG,KAAK,OAAO,sBAAsB,OAAO,SAAS,CAAC;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,MAAsC;AACvD,UAAM,WAAW,MAAM,MAAM,GAAG,KAAK,OAAO,kBAAkB;AAAA,MAC5D,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,IAAI,gBAAgB;AAAA,QACxB,YAAY;AAAA,QACZ,WAAW,KAAK;AAAA,QAChB,eAAe,KAAK;AAAA,QACpB,cAAc,KAAK;AAAA,QACnB;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,QAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACpD,YAAM,IAAI,WAAW,MAAM,qBAAqB,uCAAuC;AAAA,IACzF;AAEA,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,aAAa,cAA8C;AAC/D,UAAM,WAAW,MAAM,MAAM,GAAG,KAAK,OAAO,kBAAkB;AAAA,MAC5D,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM,IAAI,gBAAgB;AAAA,QACxB,YAAY;AAAA,QACZ,WAAW,KAAK;AAAA,QAChB,eAAe,KAAK;AAAA,QACpB,eAAe;AAAA,MACjB,CAAC;AAAA,IACH,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,QAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AACpD,YAAM,IAAI,WAAW,MAAM,qBAAqB,yBAAyB;AAAA,IAC3E;AAEA,WAAO,SAAS,KAAK;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,YAAY,aAAwC;AACxD,UAAM,WAAW,MAAM,MAAM,GAAG,KAAK,OAAO,qBAAqB;AAAA,MAC/D,SAAS;AAAA,QACP,eAAe,UAAU,WAAW;AAAA,MACtC;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,mBAAmB,2BAA2B;AAAA,IAC1D;AAEA,WAAO,SAAS,KAAK;AAAA,EACvB;AACF;","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 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 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 * 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\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 // Exchange code for tokens\n const tokens = await this.exchangeCode(code)\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 cookie\n headers.append(\n 'Set-Cookie',\n 'SIMPLELOGIN_STATE=; 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 */\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 /**\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
@@ -7,12 +7,41 @@ interface AuthorizationUrlOptions {
7
7
  state?: string;
8
8
  scopes?: string[];
9
9
  }
10
+ interface AuthorizationUrlResult {
11
+ url: string;
12
+ state: string;
13
+ /** Ready-to-use Set-Cookie header value for the state parameter */
14
+ cookie: string;
15
+ }
10
16
  interface TokenResponse {
11
17
  access_token: string;
12
18
  refresh_token: string;
19
+ token_id: string;
13
20
  token_type: string;
14
21
  expires_in: number;
15
22
  }
23
+ interface AccessTokenClaims {
24
+ sub: string;
25
+ application_id: string;
26
+ organization_id?: string;
27
+ is_master?: boolean;
28
+ type: 'access';
29
+ exp: number;
30
+ iat: number;
31
+ }
32
+ interface AuthResult {
33
+ /** Decoded claims from the access token JWT (no network call) */
34
+ claims: AccessTokenClaims;
35
+ accessToken: string;
36
+ /** Set-Cookie headers to set if tokens were refreshed */
37
+ cookies?: string[];
38
+ }
39
+ interface CallbackResult {
40
+ /** Redirect Response with token cookies set */
41
+ response: Response;
42
+ /** Full user info fetched from userinfo endpoint (store this in your BFF) */
43
+ user: UserInfo;
44
+ }
16
45
  interface UserInfo {
17
46
  sub: string;
18
47
  id: string;
@@ -41,10 +70,25 @@ declare class SimpleLogin {
41
70
  private redirectUri;
42
71
  private baseUrl;
43
72
  constructor(config?: SimpleLoginConfig);
73
+ /**
74
+ * Returns a Response that redirects to the authorization URL with the state cookie set.
75
+ * @param options - Optional scopes or custom state
76
+ * @returns A 302 redirect Response ready to be returned from your route handler
77
+ */
78
+ redirectToAuth(options?: AuthorizationUrlOptions): Response;
44
79
  /**
45
80
  * Generate the authorization URL to redirect users for sign-in
81
+ * @returns The authorization URL, state parameter, and a ready-to-use Set-Cookie header value
82
+ */
83
+ getAuthorizationUrl(options?: AuthorizationUrlOptions): AuthorizationUrlResult;
84
+ /**
85
+ * Handle the OAuth callback: verify state, exchange code, fetch user info, and return a redirect Response.
86
+ * @param request - The incoming Request object from the callback
87
+ * @param redirectTo - URL to redirect to after successful authentication (default: '/')
88
+ * @returns CallbackResult with redirect Response (cookies set) and user info to store
89
+ * @throws AuthorizationError if state is missing or doesn't match
46
90
  */
47
- getAuthorizationUrl(options?: AuthorizationUrlOptions): string;
91
+ handleCallback(request: Request, redirectTo?: string): Promise<CallbackResult>;
48
92
  /**
49
93
  * Exchange an authorization code for access and refresh tokens
50
94
  */
@@ -57,6 +101,30 @@ declare class SimpleLogin {
57
101
  * Get user information using an access token
58
102
  */
59
103
  getUserInfo(accessToken: string): Promise<UserInfo>;
104
+ /**
105
+ * Fetch and cache the public key for JWT verification
106
+ */
107
+ private getPublicKey;
108
+ /**
109
+ * Authenticate a request by verifying the access token from cookies.
110
+ * No network calls except when refreshing expired tokens.
111
+ * @param request - The incoming Request object
112
+ * @param options - Optional settings for CSRF protection
113
+ * @returns AuthResult if authenticated, null if not authenticated
114
+ */
115
+ authenticate(request: Request, options?: {
116
+ allowedOrigin?: string;
117
+ }): Promise<AuthResult | null>;
118
+ /**
119
+ * Revoke a refresh token on the IdP
120
+ */
121
+ private revokeToken;
122
+ /**
123
+ * Logout: revoke refresh token on IdP and clear cookies.
124
+ * @param request - The incoming Request object (to read refresh token from cookies)
125
+ * @param redirectTo - URL to redirect to after logout (default: '/')
126
+ */
127
+ logout(request: Request, redirectTo?: string): Promise<Response>;
60
128
  }
61
129
 
62
130
  declare class SousaError extends Error {
@@ -71,4 +139,4 @@ declare class TokenError extends SousaError {
71
139
  constructor(message: string);
72
140
  }
73
141
 
74
- export { AuthorizationError, type AuthorizationUrlOptions, SimpleLogin, type SimpleLoginConfig, SousaError, TokenError, type TokenResponse, type UserInfo };
142
+ 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
@@ -7,12 +7,41 @@ interface AuthorizationUrlOptions {
7
7
  state?: string;
8
8
  scopes?: string[];
9
9
  }
10
+ interface AuthorizationUrlResult {
11
+ url: string;
12
+ state: string;
13
+ /** Ready-to-use Set-Cookie header value for the state parameter */
14
+ cookie: string;
15
+ }
10
16
  interface TokenResponse {
11
17
  access_token: string;
12
18
  refresh_token: string;
19
+ token_id: string;
13
20
  token_type: string;
14
21
  expires_in: number;
15
22
  }
23
+ interface AccessTokenClaims {
24
+ sub: string;
25
+ application_id: string;
26
+ organization_id?: string;
27
+ is_master?: boolean;
28
+ type: 'access';
29
+ exp: number;
30
+ iat: number;
31
+ }
32
+ interface AuthResult {
33
+ /** Decoded claims from the access token JWT (no network call) */
34
+ claims: AccessTokenClaims;
35
+ accessToken: string;
36
+ /** Set-Cookie headers to set if tokens were refreshed */
37
+ cookies?: string[];
38
+ }
39
+ interface CallbackResult {
40
+ /** Redirect Response with token cookies set */
41
+ response: Response;
42
+ /** Full user info fetched from userinfo endpoint (store this in your BFF) */
43
+ user: UserInfo;
44
+ }
16
45
  interface UserInfo {
17
46
  sub: string;
18
47
  id: string;
@@ -41,10 +70,25 @@ declare class SimpleLogin {
41
70
  private redirectUri;
42
71
  private baseUrl;
43
72
  constructor(config?: SimpleLoginConfig);
73
+ /**
74
+ * Returns a Response that redirects to the authorization URL with the state cookie set.
75
+ * @param options - Optional scopes or custom state
76
+ * @returns A 302 redirect Response ready to be returned from your route handler
77
+ */
78
+ redirectToAuth(options?: AuthorizationUrlOptions): Response;
44
79
  /**
45
80
  * Generate the authorization URL to redirect users for sign-in
81
+ * @returns The authorization URL, state parameter, and a ready-to-use Set-Cookie header value
82
+ */
83
+ getAuthorizationUrl(options?: AuthorizationUrlOptions): AuthorizationUrlResult;
84
+ /**
85
+ * Handle the OAuth callback: verify state, exchange code, fetch user info, and return a redirect Response.
86
+ * @param request - The incoming Request object from the callback
87
+ * @param redirectTo - URL to redirect to after successful authentication (default: '/')
88
+ * @returns CallbackResult with redirect Response (cookies set) and user info to store
89
+ * @throws AuthorizationError if state is missing or doesn't match
46
90
  */
47
- getAuthorizationUrl(options?: AuthorizationUrlOptions): string;
91
+ handleCallback(request: Request, redirectTo?: string): Promise<CallbackResult>;
48
92
  /**
49
93
  * Exchange an authorization code for access and refresh tokens
50
94
  */
@@ -57,6 +101,30 @@ declare class SimpleLogin {
57
101
  * Get user information using an access token
58
102
  */
59
103
  getUserInfo(accessToken: string): Promise<UserInfo>;
104
+ /**
105
+ * Fetch and cache the public key for JWT verification
106
+ */
107
+ private getPublicKey;
108
+ /**
109
+ * Authenticate a request by verifying the access token from cookies.
110
+ * No network calls except when refreshing expired tokens.
111
+ * @param request - The incoming Request object
112
+ * @param options - Optional settings for CSRF protection
113
+ * @returns AuthResult if authenticated, null if not authenticated
114
+ */
115
+ authenticate(request: Request, options?: {
116
+ allowedOrigin?: string;
117
+ }): Promise<AuthResult | null>;
118
+ /**
119
+ * Revoke a refresh token on the IdP
120
+ */
121
+ private revokeToken;
122
+ /**
123
+ * Logout: revoke refresh token on IdP and clear cookies.
124
+ * @param request - The incoming Request object (to read refresh token from cookies)
125
+ * @param redirectTo - URL to redirect to after logout (default: '/')
126
+ */
127
+ logout(request: Request, redirectTo?: string): Promise<Response>;
60
128
  }
61
129
 
62
130
  declare class SousaError extends Error {
@@ -71,4 +139,4 @@ declare class TokenError extends SousaError {
71
139
  constructor(message: string);
72
140
  }
73
141
 
74
- export { AuthorizationError, type AuthorizationUrlOptions, SimpleLogin, type SimpleLoginConfig, SousaError, TokenError, type TokenResponse, type UserInfo };
142
+ 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) {
@@ -22,12 +25,35 @@ var TokenError = class extends SousaError {
22
25
 
23
26
  // src/client.ts
24
27
  var BASE_URL = "https://app.simple-login.com";
28
+ function generateState() {
29
+ const array = new Uint8Array(32);
30
+ crypto.getRandomValues(array);
31
+ return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
32
+ }
25
33
  function getEnv(key) {
26
34
  if (typeof process !== "undefined" && process.env) {
27
35
  return process.env[key];
28
36
  }
29
37
  return void 0;
30
38
  }
39
+ function parseCookie(cookieHeader, name) {
40
+ const match = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
41
+ return match ? decodeURIComponent(match[1]) : void 0;
42
+ }
43
+ var publicKeyCache = /* @__PURE__ */ new Map();
44
+ var PUBLIC_KEY_CACHE_TTL = 60 * 60 * 1e3;
45
+ function createTokenCookies(tokens) {
46
+ return [
47
+ `SIMPLELOGIN_ACCESS_TOKEN=${tokens.access_token}; HttpOnly; Secure; SameSite=Lax; Path=/`,
48
+ `SIMPLELOGIN_REFRESH_TOKEN=${tokens.refresh_token}; HttpOnly; Secure; SameSite=Lax; Path=/`
49
+ ];
50
+ }
51
+ function clearTokenCookies() {
52
+ return [
53
+ "SIMPLELOGIN_ACCESS_TOKEN=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0",
54
+ "SIMPLELOGIN_REFRESH_TOKEN=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0"
55
+ ];
56
+ }
31
57
  var SimpleLogin = class {
32
58
  clientId;
33
59
  clientSecret;
@@ -54,22 +80,82 @@ var SimpleLogin = class {
54
80
  );
55
81
  }
56
82
  }
83
+ /**
84
+ * Returns a Response that redirects to the authorization URL with the state cookie set.
85
+ * @param options - Optional scopes or custom state
86
+ * @returns A 302 redirect Response ready to be returned from your route handler
87
+ */
88
+ redirectToAuth(options = {}) {
89
+ const { url, cookie } = this.getAuthorizationUrl(options);
90
+ return new Response(null, {
91
+ status: 302,
92
+ headers: {
93
+ Location: url,
94
+ "Set-Cookie": cookie
95
+ }
96
+ });
97
+ }
57
98
  /**
58
99
  * Generate the authorization URL to redirect users for sign-in
100
+ * @returns The authorization URL, state parameter, and a ready-to-use Set-Cookie header value
59
101
  */
60
102
  getAuthorizationUrl(options = {}) {
103
+ const state = options.state ?? generateState();
61
104
  const params = new URLSearchParams({
62
105
  client_id: this.clientId,
63
106
  redirect_uri: this.redirectUri,
64
- response_type: "code"
107
+ response_type: "code",
108
+ state
65
109
  });
66
- if (options.state) {
67
- params.set("state", options.state);
68
- }
69
110
  if (options.scopes?.length) {
70
111
  params.set("scope", options.scopes.join(" "));
71
112
  }
72
- return `${this.baseUrl}/v1/auth/authorize?${params.toString()}`;
113
+ const cookie = `SIMPLELOGIN_STATE=${state}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`;
114
+ return {
115
+ url: `${this.baseUrl}/v1/auth/authorize?${params.toString()}`,
116
+ state,
117
+ cookie
118
+ };
119
+ }
120
+ /**
121
+ * Handle the OAuth callback: verify state, exchange code, fetch user info, and return a redirect Response.
122
+ * @param request - The incoming Request object from the callback
123
+ * @param redirectTo - URL to redirect to after successful authentication (default: '/')
124
+ * @returns CallbackResult with redirect Response (cookies set) and user info to store
125
+ * @throws AuthorizationError if state is missing or doesn't match
126
+ */
127
+ async handleCallback(request, redirectTo = "/") {
128
+ const url = new URL(request.url);
129
+ const code = url.searchParams.get("code");
130
+ const state = url.searchParams.get("state");
131
+ if (!code) {
132
+ throw new AuthorizationError("Missing authorization code in callback");
133
+ }
134
+ if (!state) {
135
+ throw new AuthorizationError("Missing state parameter in callback");
136
+ }
137
+ const cookieHeader = request.headers.get("cookie") ?? "";
138
+ const expectedState = parseCookie(cookieHeader, "SIMPLELOGIN_STATE");
139
+ if (!expectedState) {
140
+ throw new AuthorizationError("Missing SIMPLELOGIN_STATE cookie");
141
+ }
142
+ if (state !== expectedState) {
143
+ throw new AuthorizationError("Invalid state parameter");
144
+ }
145
+ const tokens = await this.exchangeCode(code);
146
+ const user = await this.getUserInfo(tokens.access_token);
147
+ const cookies = createTokenCookies(tokens);
148
+ const headers = new Headers();
149
+ headers.set("Location", redirectTo);
150
+ for (const cookie of cookies) {
151
+ headers.append("Set-Cookie", cookie);
152
+ }
153
+ headers.append(
154
+ "Set-Cookie",
155
+ "SIMPLELOGIN_STATE=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0"
156
+ );
157
+ const response = new Response(null, { status: 302, headers });
158
+ return { response, user };
73
159
  }
74
160
  /**
75
161
  * Exchange an authorization code for access and refresh tokens
@@ -130,6 +216,98 @@ var SimpleLogin = class {
130
216
  }
131
217
  return response.json();
132
218
  }
219
+ /**
220
+ * Fetch and cache the public key for JWT verification
221
+ */
222
+ async getPublicKey() {
223
+ const cached = publicKeyCache.get(this.clientId);
224
+ if (cached && Date.now() - cached.fetchedAt < PUBLIC_KEY_CACHE_TTL) {
225
+ return cached.key;
226
+ }
227
+ const response = await fetch(`${this.baseUrl}/api/applications/${this.clientId}/public-key`);
228
+ if (!response.ok) {
229
+ throw new AuthorizationError("Failed to fetch public key");
230
+ }
231
+ const pem = await response.text();
232
+ const key = await importSPKI(pem, "RS256");
233
+ publicKeyCache.set(this.clientId, { key, fetchedAt: Date.now() });
234
+ return key;
235
+ }
236
+ /**
237
+ * Authenticate a request by verifying the access token from cookies.
238
+ * No network calls except when refreshing expired tokens.
239
+ * @param request - The incoming Request object
240
+ * @param options - Optional settings for CSRF protection
241
+ * @returns AuthResult if authenticated, null if not authenticated
242
+ */
243
+ async authenticate(request, options) {
244
+ const method = request.method.toUpperCase();
245
+ if (method !== "GET" && method !== "HEAD" && options?.allowedOrigin) {
246
+ const origin = request.headers.get("origin") || request.headers.get("referer");
247
+ if (!origin?.startsWith(options.allowedOrigin)) {
248
+ return null;
249
+ }
250
+ }
251
+ const cookieHeader = request.headers.get("cookie") ?? "";
252
+ const accessToken = parseCookie(cookieHeader, "SIMPLELOGIN_ACCESS_TOKEN");
253
+ const refreshTokenValue = parseCookie(cookieHeader, "SIMPLELOGIN_REFRESH_TOKEN");
254
+ if (!accessToken) {
255
+ return null;
256
+ }
257
+ const publicKey = await this.getPublicKey();
258
+ try {
259
+ await jwtVerify(accessToken, publicKey);
260
+ const claims = decodeJwt(accessToken);
261
+ return { claims, accessToken };
262
+ } catch (error) {
263
+ const isExpired = error instanceof Error && "code" in error && error.code === "ERR_JWT_EXPIRED";
264
+ if (!isExpired || !refreshTokenValue) {
265
+ return null;
266
+ }
267
+ try {
268
+ const tokens = await this.refreshToken(refreshTokenValue);
269
+ const claims = decodeJwt(tokens.access_token);
270
+ const cookies = createTokenCookies(tokens);
271
+ return { claims, accessToken: tokens.access_token, cookies };
272
+ } catch {
273
+ return null;
274
+ }
275
+ }
276
+ }
277
+ /**
278
+ * Revoke a refresh token on the IdP
279
+ */
280
+ async revokeToken(refreshToken) {
281
+ await fetch(`${this.baseUrl}/v1/auth/revoke`, {
282
+ method: "POST",
283
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
284
+ body: new URLSearchParams({
285
+ token: refreshToken,
286
+ client_id: this.clientId,
287
+ client_secret: this.clientSecret
288
+ })
289
+ });
290
+ }
291
+ /**
292
+ * Logout: revoke refresh token on IdP and clear cookies.
293
+ * @param request - The incoming Request object (to read refresh token from cookies)
294
+ * @param redirectTo - URL to redirect to after logout (default: '/')
295
+ */
296
+ async logout(request, redirectTo = "/") {
297
+ const cookieHeader = request.headers.get("cookie") ?? "";
298
+ const refreshToken = parseCookie(cookieHeader, "SIMPLELOGIN_REFRESH_TOKEN");
299
+ if (refreshToken) {
300
+ this.revokeToken(refreshToken).catch(() => {
301
+ });
302
+ }
303
+ const cookies = clearTokenCookies();
304
+ const headers = new Headers();
305
+ headers.set("Location", redirectTo);
306
+ for (const cookie of cookies) {
307
+ headers.append("Set-Cookie", cookie);
308
+ }
309
+ return new Response(null, { status: 302, headers });
310
+ }
133
311
  };
134
312
  export {
135
313
  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 { AuthorizationUrlOptions, SimpleLoginConfig, TokenResponse, UserInfo } from './types'\n\ndeclare const __SIMPLELOGIN_BASE_URL__: string\n\nconst BASE_URL = __SIMPLELOGIN_BASE_URL__\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\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 * Generate the authorization URL to redirect users for sign-in\n */\n getAuthorizationUrl(options: AuthorizationUrlOptions = {}): string {\n const params = new URLSearchParams({\n client_id: this.clientId,\n redirect_uri: this.redirectUri,\n response_type: 'code',\n })\n\n if (options.state) {\n params.set('state', options.state)\n }\n\n if (options.scopes?.length) {\n params.set('scope', options.scopes.join(' '))\n }\n\n return `${this.baseUrl}/v1/auth/authorize?${params.toString()}`\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 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 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 * 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\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 // Exchange code for tokens\n const tokens = await this.exchangeCode(code)\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 cookie\n headers.append(\n 'Set-Cookie',\n 'SIMPLELOGIN_STATE=; 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 */\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 /**\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": "0.1.4",
3
+ "version": "1.1.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
  }