@simple-login/sdk 1.1.0 → 1.3.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/README.md CHANGED
@@ -23,13 +23,14 @@ Set these environment variables and the SDK will auto-detect them:
23
23
  ```bash
24
24
  SIMPLELOGIN_CLIENT_ID=your-client-id
25
25
  SIMPLELOGIN_CLIENT_SECRET=your-client-secret
26
- SIMPLELOGIN_REDIRECT_URI=https://your-app.com/callback
26
+ SIMPLELOGIN_REDIRECT_URI=https://your-app.com/auth/callback
27
+ SIMPLELOGIN_ORIGIN=https://your-app.com
27
28
  ```
28
29
 
29
30
  ```typescript
30
31
  import { SimpleLogin } from '@simple-login/sdk'
31
32
 
32
- const client = new SimpleLogin()
33
+ const simpleLogin = new SimpleLogin()
33
34
  ```
34
35
 
35
36
  ### Explicit configuration
@@ -37,27 +38,102 @@ const client = new SimpleLogin()
37
38
  You can also pass the config directly (this overrides environment variables):
38
39
 
39
40
  ```typescript
40
- import { SimpleLogin } from '@simple-login/sdk'
41
-
42
- const client = new SimpleLogin({
41
+ const simpleLogin = new SimpleLogin({
43
42
  clientId: 'your-client-id',
44
43
  clientSecret: 'your-client-secret',
45
- redirectUri: 'https://your-app.com/callback',
44
+ redirectUri: 'https://your-app.com/auth/callback',
45
+ origin: 'https://your-app.com',
46
46
  })
47
+ ```
48
+
49
+ ## Quick Start (All-in-One Flow)
50
+
51
+ The SDK provides a batteries-included flow that handles cookies, PKCE, state verification, and token refresh automatically.
52
+
53
+ ### 1. Login Route
54
+
55
+ ```typescript
56
+ // GET /auth/login
57
+ export async function loader() {
58
+ return simpleLogin.redirectToAuth()
59
+ }
60
+ ```
61
+
62
+ ### 2. Callback Route
63
+
64
+ ```typescript
65
+ // GET /auth/callback
66
+ export async function loader({ request }) {
67
+ const { response, user } = await simpleLogin.handleCallback(request, '/dashboard')
68
+
69
+ // Store user in your database if needed
70
+ await db.users.upsert({
71
+ id: user.id,
72
+ email: user.email,
73
+ name: user.name,
74
+ })
75
+
76
+ return response
77
+ }
78
+ ```
79
+
80
+ ### 3. Protected Routes
81
+
82
+ ```typescript
83
+ // GET /dashboard
84
+ export async function loader({ request }) {
85
+ const auth = await simpleLogin.authenticate(request)
47
86
 
48
- // Generate authorization URL
49
- const authUrl = client.getAuthorizationUrl({ state: 'random-state' })
87
+ if (!auth) {
88
+ return simpleLogin.redirectToAuth()
89
+ }
50
90
 
51
- // Exchange code for tokens
52
- const tokens = await client.exchangeCode(code)
91
+ // auth.claims contains decoded JWT claims (sub, application_id, etc.)
92
+ const user = await db.users.findById(auth.claims.sub)
53
93
 
54
- // Get user info
55
- const user = await client.getUserInfo(tokens.access_token)
94
+ // auth.headers contains Set-Cookie if tokens were refreshed
95
+ return json({ user }, { headers: auth.headers })
96
+ }
97
+ ```
98
+
99
+ ### 4. Logout Route
56
100
 
57
- // Refresh token
58
- const newTokens = await client.refreshToken(tokens.refresh_token)
101
+ ```typescript
102
+ // POST /auth/logout
103
+ export async function action({ request }) {
104
+ return simpleLogin.logout(request)
105
+ }
59
106
  ```
60
107
 
108
+ ## Security Features
109
+
110
+ ### PKCE (Proof Key for Code Exchange)
111
+
112
+ PKCE is automatically enabled for all authorization requests. The SDK generates a cryptographically secure code verifier and challenge, stores the verifier in an HttpOnly cookie, and includes it in the token exchange.
113
+
114
+ ### State Parameter
115
+
116
+ A cryptographically secure state parameter is automatically generated (or you can provide your own) and verified on callback to prevent CSRF attacks.
117
+
118
+ ### CSRF Protection for Mutations
119
+
120
+ The `authenticate()` method automatically checks the `Origin` or `Referer` header for non-GET requests against your configured `origin`. Returns `null` if the origin doesn't match, preventing CSRF attacks.
121
+
122
+ **Important:** You must set `SIMPLELOGIN_ORIGIN` environment variable (or pass `origin` in config) for CSRF protection to work.
123
+
124
+ ### Token Refresh
125
+
126
+ The `authenticate()` method automatically refreshes expired access tokens using the refresh token. When tokens are refreshed, the new cookies are included in `auth.headers` - just pass them to your response.
127
+
128
+ ### Secure Cookie Defaults
129
+
130
+ All cookies are set with secure defaults:
131
+
132
+ - `HttpOnly` - Not accessible via JavaScript
133
+ - `Secure` - Only sent over HTTPS
134
+ - `SameSite=Lax` - CSRF protection
135
+ - `Path=/` - Available site-wide
136
+
61
137
  ## License
62
138
 
63
139
  MIT
package/dist/index.cjs CHANGED
@@ -59,6 +59,17 @@ function generateState() {
59
59
  crypto.getRandomValues(array);
60
60
  return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
61
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
+ }
62
73
  function getEnv(key) {
63
74
  if (typeof process !== "undefined" && process.env) {
64
75
  return process.env[key];
@@ -87,11 +98,13 @@ var SimpleLogin = class {
87
98
  clientId;
88
99
  clientSecret;
89
100
  redirectUri;
101
+ origin;
90
102
  baseUrl;
91
103
  constructor(config = {}) {
92
104
  this.clientId = config.clientId ?? getEnv("SIMPLELOGIN_CLIENT_ID") ?? "";
93
105
  this.clientSecret = config.clientSecret ?? getEnv("SIMPLELOGIN_CLIENT_SECRET") ?? "";
94
106
  this.redirectUri = config.redirectUri ?? getEnv("SIMPLELOGIN_REDIRECT_URI") ?? "";
107
+ this.origin = config.origin ?? getEnv("SIMPLELOGIN_ORIGIN") ?? "";
95
108
  this.baseUrl = BASE_URL;
96
109
  if (!this.clientId) {
97
110
  throw new Error(
@@ -108,42 +121,54 @@ var SimpleLogin = class {
108
121
  "redirectUri is required. Pass it in config or set SIMPLELOGIN_REDIRECT_URI env var."
109
122
  );
110
123
  }
124
+ if (!this.origin) {
125
+ throw new Error(
126
+ "origin is required. Pass it in config or set SIMPLELOGIN_ORIGIN env var."
127
+ );
128
+ }
111
129
  }
112
130
  /**
113
- * Returns a Response that redirects to the authorization URL with the state cookie set.
131
+ * Returns a Response that redirects to the authorization URL with state and PKCE cookies set.
114
132
  * @param options - Optional scopes or custom state
115
133
  * @returns A 302 redirect Response ready to be returned from your route handler
116
134
  */
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
- });
135
+ async redirectToAuth(options = {}) {
136
+ const { url, cookies } = await this.getAuthorizationUrl(options);
137
+ const headers = new Headers();
138
+ headers.set("Location", url);
139
+ for (const cookie of cookies) {
140
+ headers.append("Set-Cookie", cookie);
141
+ }
142
+ return new Response(null, { status: 302, headers });
126
143
  }
127
144
  /**
128
145
  * 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
146
+ * @returns The authorization URL, state, code verifier, and ready-to-use Set-Cookie header values
130
147
  */
131
- getAuthorizationUrl(options = {}) {
148
+ async getAuthorizationUrl(options = {}) {
132
149
  const state = options.state ?? generateState();
150
+ const codeVerifier = generateCodeVerifier();
151
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
133
152
  const params = new URLSearchParams({
134
153
  client_id: this.clientId,
135
154
  redirect_uri: this.redirectUri,
136
155
  response_type: "code",
137
- state
156
+ state,
157
+ code_challenge: codeChallenge,
158
+ code_challenge_method: "S256"
138
159
  });
139
160
  if (options.scopes?.length) {
140
161
  params.set("scope", options.scopes.join(" "));
141
162
  }
142
- const cookie = `SIMPLELOGIN_STATE=${state}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`;
163
+ const cookies = [
164
+ `SIMPLELOGIN_STATE=${state}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`,
165
+ `SIMPLELOGIN_CODE_VERIFIER=${codeVerifier}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`
166
+ ];
143
167
  return {
144
168
  url: `${this.baseUrl}/v1/auth/authorize?${params.toString()}`,
145
169
  state,
146
- cookie
170
+ codeVerifier,
171
+ cookies
147
172
  };
148
173
  }
149
174
  /**
@@ -165,13 +190,17 @@ var SimpleLogin = class {
165
190
  }
166
191
  const cookieHeader = request.headers.get("cookie") ?? "";
167
192
  const expectedState = parseCookie(cookieHeader, "SIMPLELOGIN_STATE");
193
+ const codeVerifier = parseCookie(cookieHeader, "SIMPLELOGIN_CODE_VERIFIER");
168
194
  if (!expectedState) {
169
195
  throw new AuthorizationError("Missing SIMPLELOGIN_STATE cookie");
170
196
  }
171
197
  if (state !== expectedState) {
172
198
  throw new AuthorizationError("Invalid state parameter");
173
199
  }
174
- const tokens = await this.exchangeCode(code);
200
+ if (!codeVerifier) {
201
+ throw new AuthorizationError("Missing SIMPLELOGIN_CODE_VERIFIER cookie");
202
+ }
203
+ const tokens = await this.exchangeCode(code, codeVerifier);
175
204
  const user = await this.getUserInfo(tokens.access_token);
176
205
  const cookies = createTokenCookies(tokens);
177
206
  const headers = new Headers();
@@ -183,13 +212,19 @@ var SimpleLogin = class {
183
212
  "Set-Cookie",
184
213
  "SIMPLELOGIN_STATE=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0"
185
214
  );
215
+ headers.append(
216
+ "Set-Cookie",
217
+ "SIMPLELOGIN_CODE_VERIFIER=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0"
218
+ );
186
219
  const response = new Response(null, { status: 302, headers });
187
220
  return { response, user };
188
221
  }
189
222
  /**
190
223
  * Exchange an authorization code for access and refresh tokens
224
+ * @param code - The authorization code from the callback
225
+ * @param codeVerifier - The PKCE code verifier
191
226
  */
192
- async exchangeCode(code) {
227
+ async exchangeCode(code, codeVerifier) {
193
228
  const response = await fetch(`${this.baseUrl}/v1/auth/token`, {
194
229
  method: "POST",
195
230
  headers: {
@@ -200,7 +235,8 @@ var SimpleLogin = class {
200
235
  client_id: this.clientId,
201
236
  client_secret: this.clientSecret,
202
237
  redirect_uri: this.redirectUri,
203
- code
238
+ code,
239
+ code_verifier: codeVerifier
204
240
  })
205
241
  });
206
242
  if (!response.ok) {
@@ -265,15 +301,15 @@ var SimpleLogin = class {
265
301
  /**
266
302
  * Authenticate a request by verifying the access token from cookies.
267
303
  * No network calls except when refreshing expired tokens.
304
+ * CSRF protection is automatic using the origin from config.
268
305
  * @param request - The incoming Request object
269
- * @param options - Optional settings for CSRF protection
270
306
  * @returns AuthResult if authenticated, null if not authenticated
271
307
  */
272
- async authenticate(request, options) {
308
+ async authenticate(request) {
273
309
  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)) {
310
+ if (method !== "GET" && method !== "HEAD") {
311
+ const requestOrigin = request.headers.get("origin") || request.headers.get("referer");
312
+ if (!requestOrigin?.startsWith(this.origin)) {
277
313
  return null;
278
314
  }
279
315
  }
@@ -287,7 +323,7 @@ var SimpleLogin = class {
287
323
  try {
288
324
  await (0, import_jose.jwtVerify)(accessToken, publicKey);
289
325
  const claims = (0, import_jose.decodeJwt)(accessToken);
290
- return { claims, accessToken };
326
+ return { claims, accessToken, headers: new Headers() };
291
327
  } catch (error) {
292
328
  const isExpired = error instanceof Error && "code" in error && error.code === "ERR_JWT_EXPIRED";
293
329
  if (!isExpired || !refreshTokenValue) {
@@ -296,8 +332,11 @@ var SimpleLogin = class {
296
332
  try {
297
333
  const tokens = await this.refreshToken(refreshTokenValue);
298
334
  const claims = (0, import_jose.decodeJwt)(tokens.access_token);
299
- const cookies = createTokenCookies(tokens);
300
- return { claims, accessToken: tokens.access_token, cookies };
335
+ const headers = new Headers();
336
+ for (const cookie of createTokenCookies(tokens)) {
337
+ headers.append("Set-Cookie", cookie);
338
+ }
339
+ return { claims, accessToken: tokens.access_token, headers };
301
340
  } catch {
302
341
  return null;
303
342
  }
@@ -318,21 +357,19 @@ var SimpleLogin = class {
318
357
  });
319
358
  }
320
359
  /**
321
- * Logout: revoke refresh token on IdP and clear cookies.
360
+ * Logout: revoke refresh token on IdP, clear cookies, and redirect to auth.
322
361
  * @param request - The incoming Request object (to read refresh token from cookies)
323
- * @param redirectTo - URL to redirect to after logout (default: '/')
324
362
  */
325
- async logout(request, redirectTo = "/") {
363
+ async logout(request) {
326
364
  const cookieHeader = request.headers.get("cookie") ?? "";
327
365
  const refreshToken = parseCookie(cookieHeader, "SIMPLELOGIN_REFRESH_TOKEN");
328
366
  if (refreshToken) {
329
367
  this.revokeToken(refreshToken).catch(() => {
330
368
  });
331
369
  }
332
- const cookies = clearTokenCookies();
333
- const headers = new Headers();
334
- headers.set("Location", redirectTo);
335
- for (const cookie of cookies) {
370
+ const authResponse = await this.redirectToAuth();
371
+ const headers = new Headers(authResponse.headers);
372
+ for (const cookie of clearTokenCookies()) {
336
373
  headers.append("Set-Cookie", cookie);
337
374
  }
338
375
  return new Response(null, { status: 302, headers });
@@ -1 +1 @@
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":[]}
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 origin: 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.origin = config.origin ?? getEnv('SIMPLELOGIN_ORIGIN') ?? ''\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 if (!this.origin) {\n throw new Error(\n 'origin is required. Pass it in config or set SIMPLELOGIN_ORIGIN 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 * CSRF protection is automatic using the origin from config.\n * @param request - The incoming Request object\n * @returns AuthResult if authenticated, null if not authenticated\n */\n async authenticate(request: Request): Promise<AuthResult | null> {\n // CSRF protection for state-changing requests\n const method = request.method.toUpperCase()\n if (method !== 'GET' && method !== 'HEAD') {\n const requestOrigin = request.headers.get('origin') || request.headers.get('referer')\n if (!requestOrigin?.startsWith(this.origin)) {\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, headers: new Headers() }\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\n // Build headers with new cookies\n const headers = new Headers()\n for (const cookie of createTokenCookies(tokens)) {\n headers.append('Set-Cookie', cookie)\n }\n\n return { claims, accessToken: tokens.access_token, headers }\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, clear cookies, and redirect to auth.\n * @param request - The incoming Request object (to read refresh token from cookies)\n */\n async logout(request: Request): 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 // Get auth redirect response\n const authResponse = await this.redirectToAuth()\n\n // Merge clear cookies with auth cookies\n const headers = new Headers(authResponse.headers)\n for (const cookie of clearTokenCookies()) {\n headers.append('Set-Cookie', cookie)\n }\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
@@ -2,6 +2,8 @@ interface SimpleLoginConfig {
2
2
  clientId?: string;
3
3
  clientSecret?: string;
4
4
  redirectUri?: string;
5
+ /** Origin for CSRF protection (e.g., 'https://myapp.com'). Required for authenticate() to work. */
6
+ origin?: string;
5
7
  }
6
8
  interface AuthorizationUrlOptions {
7
9
  state?: string;
@@ -10,8 +12,9 @@ interface AuthorizationUrlOptions {
10
12
  interface AuthorizationUrlResult {
11
13
  url: string;
12
14
  state: string;
13
- /** Ready-to-use Set-Cookie header value for the state parameter */
14
- cookie: string;
15
+ codeVerifier: string;
16
+ /** Ready-to-use Set-Cookie header values for state and code verifier */
17
+ cookies: string[];
15
18
  }
16
19
  interface TokenResponse {
17
20
  access_token: string;
@@ -33,8 +36,8 @@ interface AuthResult {
33
36
  /** Decoded claims from the access token JWT (no network call) */
34
37
  claims: AccessTokenClaims;
35
38
  accessToken: string;
36
- /** Set-Cookie headers to set if tokens were refreshed */
37
- cookies?: string[];
39
+ /** Headers to include in your response (contains Set-Cookie if tokens were refreshed) */
40
+ headers: Headers;
38
41
  }
39
42
  interface CallbackResult {
40
43
  /** Redirect Response with token cookies set */
@@ -68,19 +71,20 @@ declare class SimpleLogin {
68
71
  private clientId;
69
72
  private clientSecret;
70
73
  private redirectUri;
74
+ private origin;
71
75
  private baseUrl;
72
76
  constructor(config?: SimpleLoginConfig);
73
77
  /**
74
- * Returns a Response that redirects to the authorization URL with the state cookie set.
78
+ * Returns a Response that redirects to the authorization URL with state and PKCE cookies set.
75
79
  * @param options - Optional scopes or custom state
76
80
  * @returns A 302 redirect Response ready to be returned from your route handler
77
81
  */
78
- redirectToAuth(options?: AuthorizationUrlOptions): Response;
82
+ redirectToAuth(options?: AuthorizationUrlOptions): Promise<Response>;
79
83
  /**
80
84
  * 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
85
+ * @returns The authorization URL, state, code verifier, and ready-to-use Set-Cookie header values
82
86
  */
83
- getAuthorizationUrl(options?: AuthorizationUrlOptions): AuthorizationUrlResult;
87
+ getAuthorizationUrl(options?: AuthorizationUrlOptions): Promise<AuthorizationUrlResult>;
84
88
  /**
85
89
  * Handle the OAuth callback: verify state, exchange code, fetch user info, and return a redirect Response.
86
90
  * @param request - The incoming Request object from the callback
@@ -91,8 +95,10 @@ declare class SimpleLogin {
91
95
  handleCallback(request: Request, redirectTo?: string): Promise<CallbackResult>;
92
96
  /**
93
97
  * Exchange an authorization code for access and refresh tokens
98
+ * @param code - The authorization code from the callback
99
+ * @param codeVerifier - The PKCE code verifier
94
100
  */
95
- exchangeCode(code: string): Promise<TokenResponse>;
101
+ exchangeCode(code: string, codeVerifier: string): Promise<TokenResponse>;
96
102
  /**
97
103
  * Refresh an access token using a refresh token
98
104
  */
@@ -108,23 +114,20 @@ declare class SimpleLogin {
108
114
  /**
109
115
  * Authenticate a request by verifying the access token from cookies.
110
116
  * No network calls except when refreshing expired tokens.
117
+ * CSRF protection is automatic using the origin from config.
111
118
  * @param request - The incoming Request object
112
- * @param options - Optional settings for CSRF protection
113
119
  * @returns AuthResult if authenticated, null if not authenticated
114
120
  */
115
- authenticate(request: Request, options?: {
116
- allowedOrigin?: string;
117
- }): Promise<AuthResult | null>;
121
+ authenticate(request: Request): Promise<AuthResult | null>;
118
122
  /**
119
123
  * Revoke a refresh token on the IdP
120
124
  */
121
125
  private revokeToken;
122
126
  /**
123
- * Logout: revoke refresh token on IdP and clear cookies.
127
+ * Logout: revoke refresh token on IdP, clear cookies, and redirect to auth.
124
128
  * @param request - The incoming Request object (to read refresh token from cookies)
125
- * @param redirectTo - URL to redirect to after logout (default: '/')
126
129
  */
127
- logout(request: Request, redirectTo?: string): Promise<Response>;
130
+ logout(request: Request): Promise<Response>;
128
131
  }
129
132
 
130
133
  declare class SousaError extends Error {
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ interface SimpleLoginConfig {
2
2
  clientId?: string;
3
3
  clientSecret?: string;
4
4
  redirectUri?: string;
5
+ /** Origin for CSRF protection (e.g., 'https://myapp.com'). Required for authenticate() to work. */
6
+ origin?: string;
5
7
  }
6
8
  interface AuthorizationUrlOptions {
7
9
  state?: string;
@@ -10,8 +12,9 @@ interface AuthorizationUrlOptions {
10
12
  interface AuthorizationUrlResult {
11
13
  url: string;
12
14
  state: string;
13
- /** Ready-to-use Set-Cookie header value for the state parameter */
14
- cookie: string;
15
+ codeVerifier: string;
16
+ /** Ready-to-use Set-Cookie header values for state and code verifier */
17
+ cookies: string[];
15
18
  }
16
19
  interface TokenResponse {
17
20
  access_token: string;
@@ -33,8 +36,8 @@ interface AuthResult {
33
36
  /** Decoded claims from the access token JWT (no network call) */
34
37
  claims: AccessTokenClaims;
35
38
  accessToken: string;
36
- /** Set-Cookie headers to set if tokens were refreshed */
37
- cookies?: string[];
39
+ /** Headers to include in your response (contains Set-Cookie if tokens were refreshed) */
40
+ headers: Headers;
38
41
  }
39
42
  interface CallbackResult {
40
43
  /** Redirect Response with token cookies set */
@@ -68,19 +71,20 @@ declare class SimpleLogin {
68
71
  private clientId;
69
72
  private clientSecret;
70
73
  private redirectUri;
74
+ private origin;
71
75
  private baseUrl;
72
76
  constructor(config?: SimpleLoginConfig);
73
77
  /**
74
- * Returns a Response that redirects to the authorization URL with the state cookie set.
78
+ * Returns a Response that redirects to the authorization URL with state and PKCE cookies set.
75
79
  * @param options - Optional scopes or custom state
76
80
  * @returns A 302 redirect Response ready to be returned from your route handler
77
81
  */
78
- redirectToAuth(options?: AuthorizationUrlOptions): Response;
82
+ redirectToAuth(options?: AuthorizationUrlOptions): Promise<Response>;
79
83
  /**
80
84
  * 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
85
+ * @returns The authorization URL, state, code verifier, and ready-to-use Set-Cookie header values
82
86
  */
83
- getAuthorizationUrl(options?: AuthorizationUrlOptions): AuthorizationUrlResult;
87
+ getAuthorizationUrl(options?: AuthorizationUrlOptions): Promise<AuthorizationUrlResult>;
84
88
  /**
85
89
  * Handle the OAuth callback: verify state, exchange code, fetch user info, and return a redirect Response.
86
90
  * @param request - The incoming Request object from the callback
@@ -91,8 +95,10 @@ declare class SimpleLogin {
91
95
  handleCallback(request: Request, redirectTo?: string): Promise<CallbackResult>;
92
96
  /**
93
97
  * Exchange an authorization code for access and refresh tokens
98
+ * @param code - The authorization code from the callback
99
+ * @param codeVerifier - The PKCE code verifier
94
100
  */
95
- exchangeCode(code: string): Promise<TokenResponse>;
101
+ exchangeCode(code: string, codeVerifier: string): Promise<TokenResponse>;
96
102
  /**
97
103
  * Refresh an access token using a refresh token
98
104
  */
@@ -108,23 +114,20 @@ declare class SimpleLogin {
108
114
  /**
109
115
  * Authenticate a request by verifying the access token from cookies.
110
116
  * No network calls except when refreshing expired tokens.
117
+ * CSRF protection is automatic using the origin from config.
111
118
  * @param request - The incoming Request object
112
- * @param options - Optional settings for CSRF protection
113
119
  * @returns AuthResult if authenticated, null if not authenticated
114
120
  */
115
- authenticate(request: Request, options?: {
116
- allowedOrigin?: string;
117
- }): Promise<AuthResult | null>;
121
+ authenticate(request: Request): Promise<AuthResult | null>;
118
122
  /**
119
123
  * Revoke a refresh token on the IdP
120
124
  */
121
125
  private revokeToken;
122
126
  /**
123
- * Logout: revoke refresh token on IdP and clear cookies.
127
+ * Logout: revoke refresh token on IdP, clear cookies, and redirect to auth.
124
128
  * @param request - The incoming Request object (to read refresh token from cookies)
125
- * @param redirectTo - URL to redirect to after logout (default: '/')
126
129
  */
127
- logout(request: Request, redirectTo?: string): Promise<Response>;
130
+ logout(request: Request): Promise<Response>;
128
131
  }
129
132
 
130
133
  declare class SousaError extends Error {
package/dist/index.js CHANGED
@@ -30,6 +30,17 @@ function generateState() {
30
30
  crypto.getRandomValues(array);
31
31
  return Array.from(array, (b) => b.toString(16).padStart(2, "0")).join("");
32
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
+ }
33
44
  function getEnv(key) {
34
45
  if (typeof process !== "undefined" && process.env) {
35
46
  return process.env[key];
@@ -58,11 +69,13 @@ var SimpleLogin = class {
58
69
  clientId;
59
70
  clientSecret;
60
71
  redirectUri;
72
+ origin;
61
73
  baseUrl;
62
74
  constructor(config = {}) {
63
75
  this.clientId = config.clientId ?? getEnv("SIMPLELOGIN_CLIENT_ID") ?? "";
64
76
  this.clientSecret = config.clientSecret ?? getEnv("SIMPLELOGIN_CLIENT_SECRET") ?? "";
65
77
  this.redirectUri = config.redirectUri ?? getEnv("SIMPLELOGIN_REDIRECT_URI") ?? "";
78
+ this.origin = config.origin ?? getEnv("SIMPLELOGIN_ORIGIN") ?? "";
66
79
  this.baseUrl = BASE_URL;
67
80
  if (!this.clientId) {
68
81
  throw new Error(
@@ -79,42 +92,54 @@ var SimpleLogin = class {
79
92
  "redirectUri is required. Pass it in config or set SIMPLELOGIN_REDIRECT_URI env var."
80
93
  );
81
94
  }
95
+ if (!this.origin) {
96
+ throw new Error(
97
+ "origin is required. Pass it in config or set SIMPLELOGIN_ORIGIN env var."
98
+ );
99
+ }
82
100
  }
83
101
  /**
84
- * Returns a Response that redirects to the authorization URL with the state cookie set.
102
+ * Returns a Response that redirects to the authorization URL with state and PKCE cookies set.
85
103
  * @param options - Optional scopes or custom state
86
104
  * @returns A 302 redirect Response ready to be returned from your route handler
87
105
  */
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
- });
106
+ async redirectToAuth(options = {}) {
107
+ const { url, cookies } = await this.getAuthorizationUrl(options);
108
+ const headers = new Headers();
109
+ headers.set("Location", url);
110
+ for (const cookie of cookies) {
111
+ headers.append("Set-Cookie", cookie);
112
+ }
113
+ return new Response(null, { status: 302, headers });
97
114
  }
98
115
  /**
99
116
  * 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
117
+ * @returns The authorization URL, state, code verifier, and ready-to-use Set-Cookie header values
101
118
  */
102
- getAuthorizationUrl(options = {}) {
119
+ async getAuthorizationUrl(options = {}) {
103
120
  const state = options.state ?? generateState();
121
+ const codeVerifier = generateCodeVerifier();
122
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
104
123
  const params = new URLSearchParams({
105
124
  client_id: this.clientId,
106
125
  redirect_uri: this.redirectUri,
107
126
  response_type: "code",
108
- state
127
+ state,
128
+ code_challenge: codeChallenge,
129
+ code_challenge_method: "S256"
109
130
  });
110
131
  if (options.scopes?.length) {
111
132
  params.set("scope", options.scopes.join(" "));
112
133
  }
113
- const cookie = `SIMPLELOGIN_STATE=${state}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`;
134
+ const cookies = [
135
+ `SIMPLELOGIN_STATE=${state}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`,
136
+ `SIMPLELOGIN_CODE_VERIFIER=${codeVerifier}; HttpOnly; Secure; SameSite=Lax; Max-Age=600; Path=/`
137
+ ];
114
138
  return {
115
139
  url: `${this.baseUrl}/v1/auth/authorize?${params.toString()}`,
116
140
  state,
117
- cookie
141
+ codeVerifier,
142
+ cookies
118
143
  };
119
144
  }
120
145
  /**
@@ -136,13 +161,17 @@ var SimpleLogin = class {
136
161
  }
137
162
  const cookieHeader = request.headers.get("cookie") ?? "";
138
163
  const expectedState = parseCookie(cookieHeader, "SIMPLELOGIN_STATE");
164
+ const codeVerifier = parseCookie(cookieHeader, "SIMPLELOGIN_CODE_VERIFIER");
139
165
  if (!expectedState) {
140
166
  throw new AuthorizationError("Missing SIMPLELOGIN_STATE cookie");
141
167
  }
142
168
  if (state !== expectedState) {
143
169
  throw new AuthorizationError("Invalid state parameter");
144
170
  }
145
- const tokens = await this.exchangeCode(code);
171
+ if (!codeVerifier) {
172
+ throw new AuthorizationError("Missing SIMPLELOGIN_CODE_VERIFIER cookie");
173
+ }
174
+ const tokens = await this.exchangeCode(code, codeVerifier);
146
175
  const user = await this.getUserInfo(tokens.access_token);
147
176
  const cookies = createTokenCookies(tokens);
148
177
  const headers = new Headers();
@@ -154,13 +183,19 @@ var SimpleLogin = class {
154
183
  "Set-Cookie",
155
184
  "SIMPLELOGIN_STATE=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0"
156
185
  );
186
+ headers.append(
187
+ "Set-Cookie",
188
+ "SIMPLELOGIN_CODE_VERIFIER=; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=0"
189
+ );
157
190
  const response = new Response(null, { status: 302, headers });
158
191
  return { response, user };
159
192
  }
160
193
  /**
161
194
  * Exchange an authorization code for access and refresh tokens
195
+ * @param code - The authorization code from the callback
196
+ * @param codeVerifier - The PKCE code verifier
162
197
  */
163
- async exchangeCode(code) {
198
+ async exchangeCode(code, codeVerifier) {
164
199
  const response = await fetch(`${this.baseUrl}/v1/auth/token`, {
165
200
  method: "POST",
166
201
  headers: {
@@ -171,7 +206,8 @@ var SimpleLogin = class {
171
206
  client_id: this.clientId,
172
207
  client_secret: this.clientSecret,
173
208
  redirect_uri: this.redirectUri,
174
- code
209
+ code,
210
+ code_verifier: codeVerifier
175
211
  })
176
212
  });
177
213
  if (!response.ok) {
@@ -236,15 +272,15 @@ var SimpleLogin = class {
236
272
  /**
237
273
  * Authenticate a request by verifying the access token from cookies.
238
274
  * No network calls except when refreshing expired tokens.
275
+ * CSRF protection is automatic using the origin from config.
239
276
  * @param request - The incoming Request object
240
- * @param options - Optional settings for CSRF protection
241
277
  * @returns AuthResult if authenticated, null if not authenticated
242
278
  */
243
- async authenticate(request, options) {
279
+ async authenticate(request) {
244
280
  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)) {
281
+ if (method !== "GET" && method !== "HEAD") {
282
+ const requestOrigin = request.headers.get("origin") || request.headers.get("referer");
283
+ if (!requestOrigin?.startsWith(this.origin)) {
248
284
  return null;
249
285
  }
250
286
  }
@@ -258,7 +294,7 @@ var SimpleLogin = class {
258
294
  try {
259
295
  await jwtVerify(accessToken, publicKey);
260
296
  const claims = decodeJwt(accessToken);
261
- return { claims, accessToken };
297
+ return { claims, accessToken, headers: new Headers() };
262
298
  } catch (error) {
263
299
  const isExpired = error instanceof Error && "code" in error && error.code === "ERR_JWT_EXPIRED";
264
300
  if (!isExpired || !refreshTokenValue) {
@@ -267,8 +303,11 @@ var SimpleLogin = class {
267
303
  try {
268
304
  const tokens = await this.refreshToken(refreshTokenValue);
269
305
  const claims = decodeJwt(tokens.access_token);
270
- const cookies = createTokenCookies(tokens);
271
- return { claims, accessToken: tokens.access_token, cookies };
306
+ const headers = new Headers();
307
+ for (const cookie of createTokenCookies(tokens)) {
308
+ headers.append("Set-Cookie", cookie);
309
+ }
310
+ return { claims, accessToken: tokens.access_token, headers };
272
311
  } catch {
273
312
  return null;
274
313
  }
@@ -289,21 +328,19 @@ var SimpleLogin = class {
289
328
  });
290
329
  }
291
330
  /**
292
- * Logout: revoke refresh token on IdP and clear cookies.
331
+ * Logout: revoke refresh token on IdP, clear cookies, and redirect to auth.
293
332
  * @param request - The incoming Request object (to read refresh token from cookies)
294
- * @param redirectTo - URL to redirect to after logout (default: '/')
295
333
  */
296
- async logout(request, redirectTo = "/") {
334
+ async logout(request) {
297
335
  const cookieHeader = request.headers.get("cookie") ?? "";
298
336
  const refreshToken = parseCookie(cookieHeader, "SIMPLELOGIN_REFRESH_TOKEN");
299
337
  if (refreshToken) {
300
338
  this.revokeToken(refreshToken).catch(() => {
301
339
  });
302
340
  }
303
- const cookies = clearTokenCookies();
304
- const headers = new Headers();
305
- headers.set("Location", redirectTo);
306
- for (const cookie of cookies) {
341
+ const authResponse = await this.redirectToAuth();
342
+ const headers = new Headers(authResponse.headers);
343
+ for (const cookie of clearTokenCookies()) {
307
344
  headers.append("Set-Cookie", cookie);
308
345
  }
309
346
  return new Response(null, { status: 302, headers });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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":[]}
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 origin: 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.origin = config.origin ?? getEnv('SIMPLELOGIN_ORIGIN') ?? ''\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 if (!this.origin) {\n throw new Error(\n 'origin is required. Pass it in config or set SIMPLELOGIN_ORIGIN 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 * CSRF protection is automatic using the origin from config.\n * @param request - The incoming Request object\n * @returns AuthResult if authenticated, null if not authenticated\n */\n async authenticate(request: Request): Promise<AuthResult | null> {\n // CSRF protection for state-changing requests\n const method = request.method.toUpperCase()\n if (method !== 'GET' && method !== 'HEAD') {\n const requestOrigin = request.headers.get('origin') || request.headers.get('referer')\n if (!requestOrigin?.startsWith(this.origin)) {\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, headers: new Headers() }\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\n // Build headers with new cookies\n const headers = new Headers()\n for (const cookie of createTokenCookies(tokens)) {\n headers.append('Set-Cookie', cookie)\n }\n\n return { claims, accessToken: tokens.access_token, headers }\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, clear cookies, and redirect to auth.\n * @param request - The incoming Request object (to read refresh token from cookies)\n */\n async logout(request: Request): 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 // Get auth redirect response\n const authResponse = await this.redirectToAuth()\n\n // Merge clear cookies with auth cookies\n const headers = new Headers(authResponse.headers)\n for (const cookie of clearTokenCookies()) {\n headers.append('Set-Cookie', cookie)\n }\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":";AAAA,SAAS,WAAW,YAAY,iBAAiB;;;ACA1C,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;;;ADRA,IAAM,WAAW;AAEjB,SAAS,gBAAwB;AAC/B,QAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,SAAO,gBAAgB,KAAK;AAC5B,SAAO,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAC1E;AAEA,SAAS,uBAA+B;AACtC,QAAM,QAAQ,IAAI,WAAW,EAAE;AAC/B,SAAO,gBAAgB,KAAK;AAE5B,SAAO,KAAK,OAAO,aAAa,GAAG,KAAK,CAAC,EACtC,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG,EAClB,QAAQ,MAAM,EAAE;AACrB;AAEA,eAAe,sBAAsB,UAAmC;AACtE,QAAM,UAAU,IAAI,YAAY;AAChC,QAAM,OAAO,QAAQ,OAAO,QAAQ;AACpC,QAAM,OAAO,MAAM,OAAO,OAAO,OAAO,WAAW,IAAI;AAEvD,SAAO,KAAK,OAAO,aAAa,GAAG,IAAI,WAAW,IAAI,CAAC,CAAC,EACrD,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG,EAClB,QAAQ,MAAM,EAAE;AACrB;AAEA,SAAS,OAAO,KAAiC;AAC/C,MAAI,OAAO,YAAY,eAAe,QAAQ,KAAK;AACjD,WAAO,QAAQ,IAAI,GAAG;AAAA,EACxB;AACA,SAAO;AACT;AAEA,SAAS,YAAY,cAAsB,MAAkC;AAC3E,QAAM,QAAQ,aAAa,MAAM,IAAI,OAAO,cAAc,IAAI,UAAU,CAAC;AACzE,SAAO,QAAQ,mBAAmB,MAAM,CAAC,CAAC,IAAI;AAChD;AAGA,IAAM,iBAAiB,oBAAI,IAAmD;AAC9E,IAAM,uBAAuB,KAAK,KAAK;AAEvC,SAAS,mBAAmB,QAAiC;AAC3D,SAAO;AAAA,IACL,4BAA4B,OAAO,YAAY;AAAA,IAC/C,6BAA6B,OAAO,aAAa;AAAA,EACnD;AACF;AAEA,SAAS,oBAA8B;AACrC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;AAEO,IAAM,cAAN,MAAkB;AAAA,EACf;AAAA,EACA;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,SAAS,OAAO,UAAU,OAAO,oBAAoB,KAAK;AAC/D,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;AACA,QAAI,CAAC,KAAK,QAAQ;AAChB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,eAAe,UAAmC,CAAC,GAAsB;AAC7E,UAAM,EAAE,KAAK,QAAQ,IAAI,MAAM,KAAK,oBAAoB,OAAO;AAC/D,UAAM,UAAU,IAAI,QAAQ;AAC5B,YAAQ,IAAI,YAAY,GAAG;AAC3B,eAAW,UAAU,SAAS;AAC5B,cAAQ,OAAO,cAAc,MAAM;AAAA,IACrC;AACA,WAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,QAAQ,CAAC;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,oBAAoB,UAAmC,CAAC,GAAoC;AAChG,UAAM,QAAQ,QAAQ,SAAS,cAAc;AAC7C,UAAM,eAAe,qBAAqB;AAC1C,UAAM,gBAAgB,MAAM,sBAAsB,YAAY;AAE9D,UAAM,SAAS,IAAI,gBAAgB;AAAA,MACjC,WAAW,KAAK;AAAA,MAChB,cAAc,KAAK;AAAA,MACnB,eAAe;AAAA,MACf;AAAA,MACA,gBAAgB;AAAA,MAChB,uBAAuB;AAAA,IACzB,CAAC;AAED,QAAI,QAAQ,QAAQ,QAAQ;AAC1B,aAAO,IAAI,SAAS,QAAQ,OAAO,KAAK,GAAG,CAAC;AAAA,IAC9C;AAEA,UAAM,UAAU;AAAA,MACd,qBAAqB,KAAK;AAAA,MAC1B,6BAA6B,YAAY;AAAA,IAC3C;AAEA,WAAO;AAAA,MACL,KAAK,GAAG,KAAK,OAAO,sBAAsB,OAAO,SAAS,CAAC;AAAA,MAC3D;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,eAAe,SAAkB,aAAqB,KAA8B;AACxF,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,UAAM,OAAO,IAAI,aAAa,IAAI,MAAM;AACxC,UAAM,QAAQ,IAAI,aAAa,IAAI,OAAO;AAE1C,QAAI,CAAC,MAAM;AACT,YAAM,IAAI,mBAAmB,wCAAwC;AAAA,IACvE;AAEA,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,mBAAmB,qCAAqC;AAAA,IACpE;AAEA,UAAM,eAAe,QAAQ,QAAQ,IAAI,QAAQ,KAAK;AACtD,UAAM,gBAAgB,YAAY,cAAc,mBAAmB;AACnE,UAAM,eAAe,YAAY,cAAc,2BAA2B;AAE1E,QAAI,CAAC,eAAe;AAClB,YAAM,IAAI,mBAAmB,kCAAkC;AAAA,IACjE;AAEA,QAAI,UAAU,eAAe;AAC3B,YAAM,IAAI,mBAAmB,yBAAyB;AAAA,IACxD;AAEA,QAAI,CAAC,cAAc;AACjB,YAAM,IAAI,mBAAmB,0CAA0C;AAAA,IACzE;AAGA,UAAM,SAAS,MAAM,KAAK,aAAa,MAAM,YAAY;AAGzD,UAAM,OAAO,MAAM,KAAK,YAAY,OAAO,YAAY;AAGvD,UAAM,UAAU,mBAAmB,MAAM;AACzC,UAAM,UAAU,IAAI,QAAQ;AAC5B,YAAQ,IAAI,YAAY,UAAU;AAClC,eAAW,UAAU,SAAS;AAC5B,cAAQ,OAAO,cAAc,MAAM;AAAA,IACrC;AAEA,YAAQ;AAAA,MACN;AAAA,MACA;AAAA,IACF;AACA,YAAQ;AAAA,MACN;AAAA,MACA;AAAA,IACF;AAEA,UAAM,WAAW,IAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,QAAQ,CAAC;AAE5D,WAAO,EAAE,UAAU,KAAK;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,aAAa,MAAc,cAA8C;AAC7E,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,QACA,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,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;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,eAAmC;AAC/C,UAAM,SAAS,eAAe,IAAI,KAAK,QAAQ;AAC/C,QAAI,UAAU,KAAK,IAAI,IAAI,OAAO,YAAY,sBAAsB;AAClE,aAAO,OAAO;AAAA,IAChB;AAEA,UAAM,WAAW,MAAM,MAAM,GAAG,KAAK,OAAO,qBAAqB,KAAK,QAAQ,aAAa;AAC3F,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,mBAAmB,4BAA4B;AAAA,IAC3D;AAEA,UAAM,MAAM,MAAM,SAAS,KAAK;AAChC,UAAM,MAAM,MAAM,WAAW,KAAK,OAAO;AAEzC,mBAAe,IAAI,KAAK,UAAU,EAAE,KAAK,WAAW,KAAK,IAAI,EAAE,CAAC;AAChE,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,aAAa,SAA8C;AAE/D,UAAM,SAAS,QAAQ,OAAO,YAAY;AAC1C,QAAI,WAAW,SAAS,WAAW,QAAQ;AACzC,YAAM,gBAAgB,QAAQ,QAAQ,IAAI,QAAQ,KAAK,QAAQ,QAAQ,IAAI,SAAS;AACpF,UAAI,CAAC,eAAe,WAAW,KAAK,MAAM,GAAG;AAC3C,eAAO;AAAA,MACT;AAAA,IACF;AAEA,UAAM,eAAe,QAAQ,QAAQ,IAAI,QAAQ,KAAK;AACtD,UAAM,cAAc,YAAY,cAAc,0BAA0B;AACxE,UAAM,oBAAoB,YAAY,cAAc,2BAA2B;AAE/E,QAAI,CAAC,aAAa;AAChB,aAAO;AAAA,IACT;AAEA,UAAM,YAAY,MAAM,KAAK,aAAa;AAE1C,QAAI;AAEF,YAAM,UAAU,aAAa,SAAS;AAGtC,YAAM,SAAS,UAAU,WAAW;AAEpC,aAAO,EAAE,QAAQ,aAAa,SAAS,IAAI,QAAQ,EAAE;AAAA,IACvD,SAAS,OAAO;AAEd,YAAM,YACJ,iBAAiB,SACjB,UAAU,SACT,MAA2B,SAAS;AAEvC,UAAI,CAAC,aAAa,CAAC,mBAAmB;AACpC,eAAO;AAAA,MACT;AAGA,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,aAAa,iBAAiB;AACxD,cAAM,SAAS,UAAU,OAAO,YAAY;AAG5C,cAAM,UAAU,IAAI,QAAQ;AAC5B,mBAAW,UAAU,mBAAmB,MAAM,GAAG;AAC/C,kBAAQ,OAAO,cAAc,MAAM;AAAA,QACrC;AAEA,eAAO,EAAE,QAAQ,aAAa,OAAO,cAAc,QAAQ;AAAA,MAC7D,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,YAAY,cAAqC;AAC7D,UAAM,MAAM,GAAG,KAAK,OAAO,mBAAmB;AAAA,MAC5C,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,oCAAoC;AAAA,MAC/D,MAAM,IAAI,gBAAgB;AAAA,QACxB,OAAO;AAAA,QACP,WAAW,KAAK;AAAA,QAChB,eAAe,KAAK;AAAA,MACtB,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OAAO,SAAqC;AAChD,UAAM,eAAe,QAAQ,QAAQ,IAAI,QAAQ,KAAK;AACtD,UAAM,eAAe,YAAY,cAAc,2BAA2B;AAG1E,QAAI,cAAc;AAChB,WAAK,YAAY,YAAY,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAC/C;AAGA,UAAM,eAAe,MAAM,KAAK,eAAe;AAG/C,UAAM,UAAU,IAAI,QAAQ,aAAa,OAAO;AAChD,eAAW,UAAU,kBAAkB,GAAG;AACxC,cAAQ,OAAO,cAAc,MAAM;AAAA,IACrC;AAEA,WAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,QAAQ,CAAC;AAAA,EACpD;AACF;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simple-login/sdk",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Official SDK for Simple Login",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",