Comparing version 2.0.0-beta.5 to 2.0.0-beta.6
# lucia | ||
## 2.0.0-beta.6 | ||
### Major changes | ||
- [#836](https://github.com/pilcrowOnPaper/lucia/pull/836) by [@pilcrowOnPaper](https://github.com/pilcrowOnPaper) : User ids and session ids only consist of lowercase letters and numbers by default | ||
- [#839](https://github.com/pilcrowOnPaper/lucia/pull/839) by [@pilcrowOnPaper](https://github.com/pilcrowOnPaper) : `AuthRequest.validate()` and `Auth.validateBearerToken()` throws database errors | ||
### Minor changes | ||
- [#838](https://github.com/pilcrowOnPaper/lucia/pull/838) by [@pilcrowOnPaper](https://github.com/pilcrowOnPaper) : Add `Auth.transformDatabaseUser()`, `Auth.transformDatabaseKey()`, and `Auth.transformDatabaseSession()` | ||
- [#838](https://github.com/pilcrowOnPaper/lucia/pull/838) by [@pilcrowOnPaper](https://github.com/pilcrowOnPaper) : Export `createKeyId()` | ||
## 2.0.0-beta.5 | ||
@@ -4,0 +18,0 @@ |
import type { LuciaErrorConstructor } from "../index.js"; | ||
import type { UserSchema, SessionSchema, KeySchema } from "./schema.js"; | ||
import type { UserSchema, SessionSchema, KeySchema } from "./database.js"; | ||
export type InitializeAdapter<T extends Adapter | UserAdapter | SessionAdapter> = (E: LuciaErrorConstructor) => T; | ||
@@ -27,1 +27,5 @@ export type Adapter = Readonly<{ | ||
}>; | ||
export declare const createAdapter: (adapter: InitializeAdapter<Adapter> | { | ||
user: InitializeAdapter<Adapter>; | ||
session: InitializeAdapter<SessionAdapter>; | ||
}) => Adapter; |
@@ -1,1 +0,19 @@ | ||
export {}; | ||
import { LuciaError } from "./error.js"; | ||
export const createAdapter = (adapter) => { | ||
if (!("user" in adapter)) | ||
return adapter(LuciaError); | ||
let userAdapter = adapter.user(LuciaError); | ||
let sessionAdapter = adapter.session(LuciaError); | ||
if ("getSessionAndUser" in userAdapter) { | ||
const { getSessionAndUser: _, ...extractedUserAdapter } = userAdapter; | ||
userAdapter = extractedUserAdapter; | ||
} | ||
if ("getSessionAndUser" in sessionAdapter) { | ||
const { getSessionAndUser: _, ...extractedSessionAdapter } = sessionAdapter; | ||
sessionAdapter = extractedSessionAdapter; | ||
} | ||
return { | ||
...userAdapter, | ||
...sessionAdapter | ||
}; | ||
}; |
import { AuthRequest } from "./request.js"; | ||
import { lucia as defaultMiddleware } from "../middleware/index.js"; | ||
import type { Cookie, SessionCookieAttributes } from "./cookie.js"; | ||
import type { UserSchema, SessionSchema } from "./schema.js"; | ||
import type { Adapter, SessionAdapter, InitializeAdapter } from "./adapter.js"; | ||
import type { UserSchema, SessionSchema, KeySchema } from "./database.js"; | ||
import { type Adapter, type SessionAdapter, type InitializeAdapter } from "./adapter.js"; | ||
import type { Middleware, LuciaRequest } from "./request.js"; | ||
@@ -33,3 +33,3 @@ export type Session = Readonly<{ | ||
protected middleware: _Configuration["middleware"] extends Middleware ? _Configuration["middleware"] : ReturnType<typeof defaultMiddleware>; | ||
csrfProtectionEnabled: boolean; | ||
private csrfProtectionEnabled; | ||
private allowedSubdomains; | ||
@@ -41,2 +41,3 @@ private experimental; | ||
transformDatabaseUser: (databaseUser: UserSchema) => User; | ||
transformDatabaseKey: (databaseKey: KeySchema) => Key; | ||
transformDatabaseSession: (databaseSession: SessionSchema, context: { | ||
@@ -43,0 +44,0 @@ user: User; |
@@ -8,3 +8,2 @@ import { DEFAULT_SESSION_COOKIE_NAME, createSessionCookie } from "./cookie.js"; | ||
import { isValidDatabaseSession } from "./session.js"; | ||
import { transformDatabaseKey } from "./key.js"; | ||
import { AuthRequest } from "./request.js"; | ||
@@ -15,2 +14,4 @@ import { lucia as defaultMiddleware } from "../middleware/index.js"; | ||
import { isAllowedUrl } from "../utils/url.js"; | ||
import { createAdapter } from "./adapter.js"; | ||
import { createKeyId } from "./database.js"; | ||
export const lucia = (config) => { | ||
@@ -42,21 +43,3 @@ return new Auth(config); | ||
validateConfiguration(config); | ||
if ("user" in config.adapter) { | ||
let userAdapter = config.adapter.user(LuciaError); | ||
let sessionAdapter = config.adapter.session(LuciaError); | ||
if ("getSessionAndUser" in userAdapter) { | ||
const { getSessionAndUser: _, ...extractedUserAdapter } = userAdapter; | ||
userAdapter = extractedUserAdapter; | ||
} | ||
if ("getSessionAndUser" in sessionAdapter) { | ||
const { getSessionAndUser: _, ...extractedSessionAdapter } = sessionAdapter; | ||
sessionAdapter = extractedSessionAdapter; | ||
} | ||
this.adapter = { | ||
...userAdapter, | ||
...sessionAdapter | ||
}; | ||
} | ||
else { | ||
this.adapter = config.adapter(LuciaError); | ||
} | ||
this.adapter = createAdapter(config.adapter); | ||
this.env = config.env; | ||
@@ -111,2 +94,14 @@ this.csrfProtectionEnabled = | ||
}; | ||
transformDatabaseKey = (databaseKey) => { | ||
const [providerId, ...providerUserIdSegments] = databaseKey.id.split(":"); | ||
const providerUserId = providerUserIdSegments.join(":"); | ||
const userId = databaseKey.user_id; | ||
const isPasswordDefined = !!databaseKey.hashed_password; | ||
return { | ||
providerId, | ||
providerUserId, | ||
userId, | ||
passwordDefined: isPasswordDefined | ||
}; | ||
}; | ||
transformDatabaseSession = (databaseSession, context) => { | ||
@@ -190,3 +185,3 @@ const attributes = this.getSessionAttributes(databaseSession); | ||
} | ||
const keyId = `${options.key.providerId}:${options.key.providerUserId}`; | ||
const keyId = createKeyId(options.key.providerId, options.key.providerUserId); | ||
const password = options.key.password; | ||
@@ -213,3 +208,3 @@ const hashedPassword = password | ||
useKey = async (providerId, providerUserId, password) => { | ||
const keyId = `${providerId}:${providerUserId}`; | ||
const keyId = createKeyId(providerId, providerUserId); | ||
const databaseKey = await this.adapter.getKey(keyId); | ||
@@ -241,3 +236,3 @@ if (!databaseKey) { | ||
debug.key.success("Validated key", keyId); | ||
return transformDatabaseKey(databaseKey); | ||
return this.transformDatabaseKey(databaseKey); | ||
}; | ||
@@ -407,7 +402,10 @@ getSession = async (sessionId) => { | ||
const middleware = this.middleware; | ||
return new AuthRequest(this, middleware({ | ||
args, | ||
env: this.env, | ||
cookieName: this.sessionCookie.name | ||
})); | ||
return new AuthRequest(this, { | ||
context: middleware({ | ||
args, | ||
env: this.env, | ||
cookieName: this.sessionCookie.name | ||
}), | ||
csrfProtectionEnabled: this.csrfProtectionEnabled | ||
}); | ||
}; | ||
@@ -421,3 +419,3 @@ createSessionCookie = (session) => { | ||
createKey = async (options) => { | ||
const keyId = `${options.providerId}:${options.providerUserId}`; | ||
const keyId = createKeyId(options.providerId, options.providerUserId); | ||
let hashedPassword = null; | ||
@@ -441,7 +439,7 @@ if (options.password !== null) { | ||
deleteKey = async (providerId, providerUserId) => { | ||
const keyId = `${providerId}:${providerUserId}`; | ||
const keyId = createKeyId(providerId, providerUserId); | ||
await this.adapter.deleteKey(keyId); | ||
}; | ||
getKey = async (providerId, providerUserId) => { | ||
const keyId = `${providerId}:${providerUserId}`; | ||
const keyId = createKeyId(providerId, providerUserId); | ||
const databaseKey = await this.adapter.getKey(keyId); | ||
@@ -451,3 +449,3 @@ if (!databaseKey) { | ||
} | ||
const key = transformDatabaseKey(databaseKey); | ||
const key = this.transformDatabaseKey(databaseKey); | ||
return key; | ||
@@ -460,6 +458,6 @@ }; | ||
]); | ||
return databaseKeys.map((databaseKey) => transformDatabaseKey(databaseKey)); | ||
return databaseKeys.map((databaseKey) => this.transformDatabaseKey(databaseKey)); | ||
}; | ||
updateKeyPassword = async (providerId, providerUserId, password) => { | ||
const keyId = `${providerId}:${providerUserId}`; | ||
const keyId = createKeyId(providerId, providerUserId); | ||
const hashedPassword = password === null ? null : await this.passwordHash.generate(password); | ||
@@ -466,0 +464,0 @@ await this.adapter.updateKey(keyId, { |
@@ -22,6 +22,9 @@ import type { Auth, Env, Session } from "./index.js"; | ||
}) => RequestContext; | ||
export declare class AuthRequest<A extends Auth = any> { | ||
export declare class AuthRequest<_Auth extends Auth = any> { | ||
private auth; | ||
private context; | ||
constructor(auth: A, context: RequestContext); | ||
constructor(auth: _Auth, { context, csrfProtectionEnabled }: { | ||
context: RequestContext; | ||
csrfProtectionEnabled: boolean; | ||
}); | ||
private validatePromise; | ||
@@ -28,0 +31,0 @@ private validateBearerTokenPromise; |
import { debug } from "../utils/debug.js"; | ||
import { LuciaError } from "./error.js"; | ||
export class AuthRequest { | ||
auth; | ||
context; | ||
constructor(auth, context) { | ||
constructor(auth, { context, csrfProtectionEnabled }) { | ||
debug.request.init(context.request.method, context.request.url); | ||
@@ -10,3 +11,3 @@ this.auth = auth; | ||
try { | ||
if (auth.csrfProtectionEnabled) { | ||
if (csrfProtectionEnabled) { | ||
auth.validateRequestOrigin(context.request); | ||
@@ -67,5 +68,8 @@ } | ||
} | ||
catch { | ||
this.setSessionCookie(null); | ||
return resolve(null); | ||
catch (e) { | ||
if (e instanceof LuciaError) { | ||
this.setSessionCookie(null); | ||
return resolve(null); | ||
} | ||
throw e; | ||
} | ||
@@ -89,4 +93,7 @@ }); | ||
} | ||
catch { | ||
return resolve(null); | ||
catch (e) { | ||
if (e instanceof LuciaError) { | ||
return resolve(null); | ||
} | ||
throw e; | ||
} | ||
@@ -93,0 +100,0 @@ }); |
@@ -1,2 +0,2 @@ | ||
import type { SessionSchema } from "./schema.js"; | ||
import type { SessionSchema } from "./database.js"; | ||
export declare const isValidDatabaseSession: (databaseSession: SessionSchema) => boolean; |
export { lucia } from "./auth/index.js"; | ||
export { DEFAULT_SESSION_COOKIE_NAME } from "./auth/cookie.js"; | ||
export { LuciaError } from "./auth/error.js"; | ||
export { createKeyId } from "./auth/database.js"; | ||
export type GlobalAuth = Lucia.Auth; | ||
@@ -9,5 +10,5 @@ export type GlobalDatabaseUserAttributes = Lucia.DatabaseUserAttributes; | ||
export type { Adapter, InitializeAdapter, UserAdapter, SessionAdapter } from "./auth/adapter.js"; | ||
export type { UserSchema, KeySchema, SessionSchema } from "./auth/schema.js"; | ||
export type { UserSchema, KeySchema, SessionSchema } from "./auth/database.js"; | ||
export type { RequestContext, Middleware, AuthRequest } from "./auth/request.js"; | ||
export type { Cookie } from "./auth/cookie.js"; | ||
export type { LuciaErrorConstructor } from "./auth/error.js"; |
export { lucia } from "./auth/index.js"; | ||
export { DEFAULT_SESSION_COOKIE_NAME } from "./auth/cookie.js"; | ||
export { LuciaError } from "./auth/error.js"; | ||
export { createKeyId } from "./auth/database.js"; |
export declare const parseCookie: (str: string) => Record<string, string>; | ||
export type CookieAttributes = { | ||
domain?: string | undefined; | ||
encode?: (value: string) => string; | ||
expires?: Date | undefined; | ||
httpOnly?: boolean | undefined; | ||
maxAge?: number | undefined; | ||
path?: string | undefined; | ||
priority?: "low" | "medium" | "high" | undefined; | ||
sameSite?: true | false | "lax" | "strict" | "none" | undefined; | ||
secure?: boolean | undefined; | ||
}; | ||
export type CookieAttributes = Partial<{ | ||
domain: string; | ||
encode: (value: string) => string; | ||
expires: Date; | ||
httpOnly: boolean; | ||
maxAge: number; | ||
path: string; | ||
priority: "low" | "medium" | "high"; | ||
sameSite: true | false | "lax" | "strict" | "none"; | ||
secure: boolean; | ||
}>; | ||
type CookieSerializeOptions = CookieAttributes; | ||
export declare const serializeCookie: (name: string, val: string, options?: CookieSerializeOptions) => string; | ||
export {}; |
@@ -9,9 +9,3 @@ /* | ||
*/ | ||
const __toString = Object.prototype.toString; | ||
// eslint-disable-next-line no-control-regex | ||
const fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/; | ||
export const parseCookie = (str) => { | ||
if (typeof str !== "string") { | ||
throw new TypeError("argument str must be a string"); | ||
} | ||
const obj = {}; | ||
@@ -48,23 +42,16 @@ let index = 0; | ||
const opt = options ?? {}; | ||
const enc = opt.encode ?? encode; | ||
if (!fieldContentRegExp.test(name)) | ||
throw new TypeError("argument name is invalid"); | ||
const enc = opt.encode ?? encodeURIComponent; | ||
const value = enc(val); | ||
if (value && !fieldContentRegExp.test(value)) | ||
throw new TypeError("argument val is invalid"); | ||
let str = name + "=" + value; | ||
if (null != opt.maxAge) { | ||
const maxAge = opt.maxAge - 0; | ||
if (isNaN(maxAge) || !isFinite(maxAge)) | ||
if (isNaN(maxAge) || !isFinite(maxAge)) { | ||
throw new TypeError("option maxAge is invalid"); | ||
} | ||
str += "; Max-Age=" + Math.floor(maxAge); | ||
} | ||
if (opt.domain) { | ||
if (!fieldContentRegExp.test(opt.domain)) | ||
throw new TypeError("option domain is invalid"); | ||
str += "; Domain=" + opt.domain; | ||
} | ||
if (opt.path) { | ||
if (!fieldContentRegExp.test(opt.path)) | ||
throw new TypeError("option path is invalid"); | ||
str += "; Path=" + opt.path; | ||
@@ -74,4 +61,2 @@ } | ||
const expires = opt.expires; | ||
if (!isDate(expires) || isNaN(expires.valueOf())) | ||
throw new TypeError("option expires is invalid"); | ||
str += "; Expires=" + expires.toUTCString(); | ||
@@ -126,14 +111,5 @@ } | ||
}; | ||
const decode = (str) => { | ||
return str.includes("%") ? decodeURIComponent(str) : str; | ||
}; | ||
const encode = (val) => { | ||
return encodeURIComponent(val); | ||
}; | ||
const isDate = (val) => { | ||
return __toString.call(val) === "[object Date]" || val instanceof Date; | ||
}; | ||
const tryDecode = (str) => { | ||
try { | ||
return decode(str); | ||
return decodeURIComponent(str); | ||
} | ||
@@ -140,0 +116,0 @@ catch (e) { |
@@ -14,2 +14,3 @@ const DEBUG_GLOBAL = "__lucia_debug_mode"; | ||
const globalContext = globalThis; | ||
globalContext[DEBUG_GLOBAL] = false; | ||
const format = (text, format, removeFormat) => { | ||
@@ -45,4 +46,8 @@ return `${format}${text}${removeFormat ? removeFormat : DEFAULT_TEXT_FORMAT}`; | ||
}; | ||
export const bold = (text) => format(text, `${ESCAPE}[1m`, `${ESCAPE}[22m`); | ||
const dim = (text) => format(text, `${ESCAPE}[2m`, `${ESCAPE}[22m`); | ||
export const bold = (text) => { | ||
return format(text, `${ESCAPE}[1m`, `${ESCAPE}[22m`); | ||
}; | ||
const dim = (text) => { | ||
return format(text, `${ESCAPE}[2m`, `${ESCAPE}[22m`); | ||
}; | ||
const isDebugModeEnabled = () => { | ||
@@ -49,0 +54,0 @@ return Boolean(globalContext[DEBUG_GLOBAL]); |
// code copied from Nanoid: | ||
// https://github.com/ai/nanoid/blob/9b748729f8ad5409503b508b65958636e55bd87a/index.browser.js | ||
// nanoid uses Node dependencies on default bundler settings | ||
const getRandomValues = (bytes) => crypto.getRandomValues(new Uint8Array(bytes)); | ||
const DEFAULT_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"; | ||
const getRandomValues = (bytes) => { | ||
return crypto.getRandomValues(new Uint8Array(bytes)); | ||
}; | ||
const DEFAULT_ALPHABET = "abcdefghijklmnopqrstuvwxyz1234567890"; | ||
export const generateRandomString = (size, alphabet = DEFAULT_ALPHABET) => { | ||
@@ -7,0 +9,0 @@ const mask = (2 << (Math.log(alphabet.length - 1) / Math.LN2)) - 1; |
{ | ||
"name": "lucia", | ||
"version": "2.0.0-beta.5", | ||
"version": "2.0.0-beta.6", | ||
"description": "A simple and flexible authentication library", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
77079
1836
46