diff --git a/documentation-v2/content/main/0.start-here/0.getting-started/astro.md b/documentation-v2/content/main/0.start-here/0.getting-started/astro.md index 9ca365b37..81329f3c4 100644 --- a/documentation-v2/content/main/0.start-here/0.getting-started/astro.md +++ b/documentation-v2/content/main/0.start-here/0.getting-started/astro.md @@ -60,6 +60,7 @@ const auth = lucia({ - [`postgres`](/database-adapters/postgres): PostgreSQL - [Prisma](/database-adapters/prisma): MongoDB, MySQL, PostgreSQL, SQLite - [Redis](/database-adapters/redis): Redis +- [Unstorage](/database-adapters/unstorage): Azure, Cloudflare KV, Memory, MongoDB, Planetscale, Redis, Vercel KV ### Provider specific adapters diff --git a/documentation-v2/content/main/0.start-here/0.getting-started/express.md b/documentation-v2/content/main/0.start-here/0.getting-started/express.md index fb1e766d3..8bd86556a 100644 --- a/documentation-v2/content/main/0.start-here/0.getting-started/express.md +++ b/documentation-v2/content/main/0.start-here/0.getting-started/express.md @@ -71,6 +71,7 @@ const auth = lucia({ - [`postgres`](/database-adapters/postgres): PostgreSQL - [Prisma](/database-adapters/prisma): MongoDB, MySQL, PostgreSQL, SQLite - [Redis](/database-adapters/redis): Redis +- [Unstorage](/database-adapters/unstorage): Azure, Cloudflare KV, Memory, MongoDB, Planetscale, Redis, Vercel KV ### Provider specific adapters diff --git a/documentation-v2/content/main/0.start-here/0.getting-started/index.md b/documentation-v2/content/main/0.start-here/0.getting-started/index.md index 0ac1c722e..96fe8179f 100644 --- a/documentation-v2/content/main/0.start-here/0.getting-started/index.md +++ b/documentation-v2/content/main/0.start-here/0.getting-started/index.md @@ -90,6 +90,7 @@ const auth = lucia({ - [`postgres`](/database-adapters/postgres): PostgreSQL - [Prisma](/database-adapters/prisma): MongoDB, MySQL, PostgreSQL, SQLite - [Redis](/database-adapters/redis): Redis +- [Unstorage](/database-adapters/unstorage): Azure, Cloudflare KV, Memory, MongoDB, Planetscale, Redis, Vercel KV ### Provider specific adapters diff --git a/documentation-v2/content/main/0.start-here/0.getting-started/nextjs-app.md b/documentation-v2/content/main/0.start-here/0.getting-started/nextjs-app.md index dcdc08794..886b9e770 100644 --- a/documentation-v2/content/main/0.start-here/0.getting-started/nextjs-app.md +++ b/documentation-v2/content/main/0.start-here/0.getting-started/nextjs-app.md @@ -66,6 +66,7 @@ const auth = lucia({ - [`postgres`](/database-adapters/postgres): PostgreSQL - [Prisma](/database-adapters/prisma): MongoDB, MySQL, PostgreSQL, SQLite - [Redis](/database-adapters/redis): Redis +- [Unstorage](/database-adapters/unstorage): Azure, Cloudflare KV, Memory, MongoDB, Planetscale, Redis, Vercel KV ### Provider specific adapters diff --git a/documentation-v2/content/main/0.start-here/0.getting-started/nextjs-pages.md b/documentation-v2/content/main/0.start-here/0.getting-started/nextjs-pages.md index 535924751..bfd0c82b0 100644 --- a/documentation-v2/content/main/0.start-here/0.getting-started/nextjs-pages.md +++ b/documentation-v2/content/main/0.start-here/0.getting-started/nextjs-pages.md @@ -66,6 +66,7 @@ const auth = lucia({ - [`postgres`](/database-adapters/postgres): PostgreSQL - [Prisma](/database-adapters/prisma): MongoDB, MySQL, PostgreSQL, SQLite - [Redis](/database-adapters/redis): Redis +- [Unstorage](/database-adapters/unstorage): Azure, Cloudflare KV, Memory, MongoDB, Planetscale, Redis, Vercel KV ### Provider specific adapters diff --git a/documentation-v2/content/main/0.start-here/0.getting-started/nuxt.md b/documentation-v2/content/main/0.start-here/0.getting-started/nuxt.md index d437e685a..f7f4ad1f6 100644 --- a/documentation-v2/content/main/0.start-here/0.getting-started/nuxt.md +++ b/documentation-v2/content/main/0.start-here/0.getting-started/nuxt.md @@ -60,6 +60,7 @@ const auth = lucia({ - [`postgres`](/database-adapters/postgres): PostgreSQL - [Prisma](/database-adapters/prisma): MongoDB, MySQL, PostgreSQL, SQLite - [Redis](/database-adapters/redis): Redis +- [Unstorage](/database-adapters/unstorage): Azure, Cloudflare KV, Memory, MongoDB, Planetscale, Redis, Vercel KV ### Provider specific adapters diff --git a/documentation-v2/content/main/0.start-here/0.getting-started/remix.md b/documentation-v2/content/main/0.start-here/0.getting-started/remix.md index ea32c6bf7..2ce547e32 100644 --- a/documentation-v2/content/main/0.start-here/0.getting-started/remix.md +++ b/documentation-v2/content/main/0.start-here/0.getting-started/remix.md @@ -82,6 +82,7 @@ const auth = lucia({ - [`postgres`](/database-adapters/postgres): PostgreSQL - [Prisma](/database-adapters/prisma): MongoDB, MySQL, PostgreSQL, SQLite - [Redis](/database-adapters/redis): Redis +- [Unstorage](/database-adapters/unstorage): Azure, Cloudflare KV, Memory, MongoDB, Planetscale, Redis, Vercel KV ### Provider specific adapters diff --git a/documentation-v2/content/main/0.start-here/0.getting-started/sveltekit.md b/documentation-v2/content/main/0.start-here/0.getting-started/sveltekit.md index 364727314..912a51fe3 100644 --- a/documentation-v2/content/main/0.start-here/0.getting-started/sveltekit.md +++ b/documentation-v2/content/main/0.start-here/0.getting-started/sveltekit.md @@ -63,6 +63,7 @@ const auth = lucia({ - [`postgres`](/database-adapters/postgres): PostgreSQL - [Prisma](/database-adapters/prisma): MongoDB, MySQL, PostgreSQL, SQLite - [Redis](/database-adapters/redis): Redis +- [Unstorage](/database-adapters/unstorage): Azure, Cloudflare KV, Memory, MongoDB, Planetscale, Redis, Vercel KV ### Provider specific adapters diff --git a/documentation-v2/content/main/1.basics/0.database.md b/documentation-v2/content/main/1.basics/0.database.md index 67205302c..ed53ac4b2 100644 --- a/documentation-v2/content/main/1.basics/0.database.md +++ b/documentation-v2/content/main/1.basics/0.database.md @@ -22,7 +22,7 @@ We currently provide the following adapters: - [PlanetScale serverless](/database-adapters/planetscale-serverless) - [Prisma](/database-adapters/prisma) - [Redis](/database-adapters/redis) -- [Upstash](/database-adapters/upstash) +- [Unstorage](/database-adapters/unstorage) You can also use query builders like Drizzle ORM and Kysely since they rely on underlying drivers that we provide adapters for. diff --git a/documentation-v2/content/main/2.database-adapters/redis.md b/documentation-v2/content/main/2.database-adapters/redis.md index 423726401..e0a13dc43 100644 --- a/documentation-v2/content/main/2.database-adapters/redis.md +++ b/documentation-v2/content/main/2.database-adapters/redis.md @@ -27,6 +27,14 @@ const redis: ( | `client` | `RedisClientType` | | Redis client | | `prefixes` | `Record` | ✓ | Key prefixes | +## Installation + +``` +npm i @lucia-auth/adapter-session-redis +pnpm add @lucia-auth/adapter-session-redis +yarn add @lucia-auth/adapter-session-redis +``` + ### Key prefixes Key are defined as a combination of a prefix and an id so everything can be stored in a single Redis instance. By default, sessions are stored as `session:` and user-sessions relationships are stored as `user_sessions:`. diff --git a/documentation-v2/content/main/2.database-adapters/unstorage.md b/documentation-v2/content/main/2.database-adapters/unstorage.md new file mode 100644 index 000000000..ea446b91e --- /dev/null +++ b/documentation-v2/content/main/2.database-adapters/unstorage.md @@ -0,0 +1,58 @@ +--- +menuTitle: "Unstorage" +title: "Unstorage session adapter" +description: "Learn how to use Unstorage with Lucia" +--- + +Session adapter for [Unstorage](https://github.com/unjs/unstorage). This only handles sessions, and not users or keys. Supports many key-value databases, including Azure, Cloudflare KV, MongoDB, Planetscale, Redis, and Vercel KV, as well as in-memory. + +```ts +import { unstorage } from "@lucia-auth/adapter-session-unstorage"; +``` + +```ts +const unstorage: ( + storage: Storage, + prefixes?: { + session: string; + userSession: string; + } +) => InitializeAdapter; +``` + +##### Parameters + +| name | type | optional | description | +| ---------- | ------------------------ | :------: | ------------ | +| `storage` | `Storage` | | | +| `prefixes` | `Record` | ✓ | Key prefixes | + +## Installation + +``` +npm i @lucia-auth/adapter-session-unstorage +pnpm add @lucia-auth/adapter-session-unstorage +yarn add @lucia-auth/adapter-session-unstorage +``` + +### Key prefixes + +Key are defined as a combination of a prefix and an id so everything can be stored in a single storage instance. By default, sessions are stored as `session:` and user-sessions relationships are stored as `user_sessions:`. + +## Usage + +```ts +import { lucia } from "lucia"; +import { unstorage } from "@lucia-auth/adapter-session-unstorage"; +import { createStorage } from "unstorage"; + +const storage = createStorage() + +const auth = lucia({ + adapter: { + user: userAdapter, // any normal adapter for storing users/keys + session: unstorage(storage) + } + // ... +}); +``` diff --git a/documentation-v2/content/reference/index.md b/documentation-v2/content/reference/index.md index 648a8eb92..e91d16998 100644 --- a/documentation-v2/content/reference/index.md +++ b/documentation-v2/content/reference/index.md @@ -42,3 +42,7 @@ order: -1 ### `@lucia-auth/adapter-session-redis` - [`redis()`](/database-adapters/redis) + +### `@lucia-auth/adapter-session-unstorage` + +- [`redis()`](/database-adapters/unstorage) diff --git a/documentation/content/main/start-here/getting-started.astro.md b/documentation/content/main/start-here/getting-started.astro.md index 025d3a73f..d7ba33eb0 100644 --- a/documentation/content/main/start-here/getting-started.astro.md +++ b/documentation/content/main/start-here/getting-started.astro.md @@ -25,8 +25,8 @@ We currently support the following database/ORM options: - [PlanetScale serverless](/adapters/planetscale) - [PostgreSQL](/adapters/postgresql) - [Prisma](/adapters/prisma) -- [Redis](/adapters/redis) - [SQLite](/adapters/sqlite) +- [Redis](/adapters/redis) ## Initialize Lucia diff --git a/documentation/content/main/start-here/getting-started.md b/documentation/content/main/start-here/getting-started.md index cc85a3561..ed4bff813 100644 --- a/documentation/content/main/start-here/getting-started.md +++ b/documentation/content/main/start-here/getting-started.md @@ -35,8 +35,8 @@ We currently support the following database/ORM options: - [PlanetScale serverless](/adapters/planetscale) - [PostgreSQL](/adapters/postgresql) - [Prisma](/adapters/prisma) -- [Redis](/adapters/redis) - [SQLite](/adapters/sqlite) +- [Redis](/adapters/redis) ## Initialize Lucia diff --git a/documentation/content/main/start-here/getting-started.nextjs.md b/documentation/content/main/start-here/getting-started.nextjs.md index 10353722b..a2ed6ad23 100644 --- a/documentation/content/main/start-here/getting-started.nextjs.md +++ b/documentation/content/main/start-here/getting-started.nextjs.md @@ -29,8 +29,8 @@ We currently support the following database/ORM options: - [PlanetScale serverless](/adapters/planetscale) - [PostgreSQL](/adapters/postgresql) - [Prisma](/adapters/prisma) -- [Redis](/adapters/redis) - [SQLite](/adapters/sqlite) +- [Redis](/adapters/redis) ## Initialize Lucia diff --git a/documentation/content/main/start-here/getting-started.nuxt.md b/documentation/content/main/start-here/getting-started.nuxt.md index e31847686..aa3e8f297 100644 --- a/documentation/content/main/start-here/getting-started.nuxt.md +++ b/documentation/content/main/start-here/getting-started.nuxt.md @@ -25,8 +25,8 @@ We currently support the following database/ORM options: - [PlanetScale serverless](/adapters/planetscale) - [PostgreSQL](/adapters/postgresql) - [Prisma](/adapters/prisma) -- [Redis](/adapters/redis) - [SQLite](/adapters/sqlite) +- [Redis](/adapters/redis) ## Initialize Lucia diff --git a/documentation/content/main/start-here/getting-started.qwik.md b/documentation/content/main/start-here/getting-started.qwik.md index e289650aa..3fccb400a 100644 --- a/documentation/content/main/start-here/getting-started.qwik.md +++ b/documentation/content/main/start-here/getting-started.qwik.md @@ -25,8 +25,8 @@ We currently support the following database/ORM options: - [PlanetScale serverless](/adapters/planetscale) - [PostgreSQL](/adapters/postgresql) - [Prisma](/adapters/prisma) -- [Redis](/adapters/redis) - [SQLite](/adapters/sqlite) +- [Redis](/adapters/redis) ## Initialize Lucia diff --git a/documentation/content/main/start-here/getting-started.remix.md b/documentation/content/main/start-here/getting-started.remix.md index 6cefdd3c2..2899b3681 100644 --- a/documentation/content/main/start-here/getting-started.remix.md +++ b/documentation/content/main/start-here/getting-started.remix.md @@ -25,8 +25,8 @@ We currently support the following database/ORM options: - [PlanetScale serverless](/adapters/planetscale) - [PostgreSQL](/adapters/postgresql) - [Prisma](/adapters/prisma) -- [Redis](/adapters/redis) - [SQLite](/adapters/sqlite) +- [Redis](/adapters/redis) ## Initialize Lucia diff --git a/documentation/content/main/start-here/getting-started.sveltekit.md b/documentation/content/main/start-here/getting-started.sveltekit.md index 6428fde4f..78e11d221 100644 --- a/documentation/content/main/start-here/getting-started.sveltekit.md +++ b/documentation/content/main/start-here/getting-started.sveltekit.md @@ -25,8 +25,8 @@ We currently support the following database/ORM options: - [PlanetScale serverless](/adapters/planetscale) - [PostgreSQL](/adapters/postgresql) - [Prisma](/adapters/prisma) -- [Redis](/adapters/redis) - [SQLite](/adapters/sqlite) +- [Redis](/adapters/redis) ## Initialize Lucia diff --git a/documentation/content/main/start-here/introduction.md b/documentation/content/main/start-here/introduction.md index 8e0ec5908..3990896b8 100644 --- a/documentation/content/main/start-here/introduction.md +++ b/documentation/content/main/start-here/introduction.md @@ -77,7 +77,7 @@ Lucia is a sever-side library, so it does not provide any client side helpers. - MySQL adapters: `@lucia-auth/adapter-mysql` - PostgreSQL adapters: `@lucia-auth/adapter-postgresql` - Prisma adapter: `@lucia-auth/adapter-prisma` -- Redis adapter: `@lucia-auth/adapter-session-redis` - SQLite adapters: `@lucia-auth/adapter-sqlite` - OAuth integration: `@lucia-auth/oauth` - Tokens integration: `@lucia-auth/tokens` +- Redis adapter: `@lucia-auth/adapter-session-redis` diff --git a/documentation/content/main/start-here/username-password.nuxt.md b/documentation/content/main/start-here/username-password.nuxt.md index 036a38399..8b1f8983d 100644 --- a/documentation/content/main/start-here/username-password.nuxt.md +++ b/documentation/content/main/start-here/username-password.nuxt.md @@ -78,7 +78,7 @@ const handleSubmit = async (e: Event) => { }); navigateTo("/"); } catch (error) { - console.log(error); + console.error(error); } }; @@ -194,7 +194,7 @@ const handleSubmit = async (e: Event) => { }); navigateTo("/"); } catch (error) { - console.log(error); + console.error(error); } }; @@ -294,11 +294,10 @@ In both the signup and login page, fetch the current user with `useFetch()`, and @@ -316,19 +315,16 @@ Create `pages/index.vue`. This page will show the user's data. Redirect the user @@ -363,3 +359,54 @@ export default defineEventHandler(async (event) => { return null; }); ``` + +### Handle authentication with state and middleware + +Using middlewares and composables is a really neat way to avoid duplicating your authorization logic. + +Create a `useAuth` composable : + +```ts +// composables/useAuth.ts +import type { User } from "lucia-auth"; + +export const useAuth = () => { + const { data, execute: fetchUser } = useFetch("/api/user", { + immediate: false + }); + return { user: computed(() => data.value?.user), fetchUser }; +}; +``` + +Create an `auth` middleware : + +```ts +// middlewares/auth.ts +export default defineNuxtRouteMiddleware(async () => { + const { user, fetchUser } = useAuth(); + await fetchUser(); + if (!user.value) { + // if there's no user found, navigate to the login page. + return navigateTo("/login"); + } +}); +``` + +Then in your vue files ... + +```vue + + + +``` diff --git a/documentation/content/oauth/start-here/getting-started.nuxt.md b/documentation/content/oauth/start-here/getting-started.nuxt.md index 627d11721..c9bcfa6b2 100644 --- a/documentation/content/oauth/start-here/getting-started.nuxt.md +++ b/documentation/content/oauth/start-here/getting-started.nuxt.md @@ -44,6 +44,7 @@ The state may not be returned depending on the provider, and it may return PKCE ```ts // server/api/oauth/index.get.ts export default defineEventHandler(async (event) => { + // const { provider } = getQuery(event); You can grab the provider like this /api/oauth?provider=github const [url, state] = await githubAuth.getAuthorizationUrl(); setCookie(event, "oauth_state", state, { path: "/", @@ -51,14 +52,14 @@ export default defineEventHandler(async (event) => { httpOnly: true, secure: process.dev ? false : true }); - return await sendRedirect(event, url.toString(), 302); + return sendRedirect(event, url.toString(), 302); }); ``` Alternatively, you can embed the url from `getAuthorizationUrl()` inside an anchor tag. -```svelte -Sign in with provider +```html +Sign in with provider ``` > (red) Keep in mind while sending the result of `getAuthorizationUrl()` to the client is fine, **the provider oauth instance (`providerAuth`) should only be inside a server context**. You will leak your API keys if you import it in the client. @@ -77,8 +78,8 @@ export default defineEventHandler(async (event) => { const authRequest = auth.handleRequest(event); // get code and state params from url const query = getQuery(event); - const code = query.code?.toString() ?? null; - const state = query.state?.toString() ?? null; + const code = query.code?.toString(); + const state = query.state?.toString(); // get stored state from cookies const storedState = getCookie(event, "oauth_state"); diff --git a/examples/nuxt/github-oauth/.npmrc b/examples/nuxt/github-oauth/.npmrc index c483022c0..bf2e7648b 100644 --- a/examples/nuxt/github-oauth/.npmrc +++ b/examples/nuxt/github-oauth/.npmrc @@ -1 +1 @@ -shamefully-hoist=true \ No newline at end of file +shamefully-hoist=true diff --git a/examples/nuxt/username-and-password/.npmrc b/examples/nuxt/username-and-password/.npmrc index c483022c0..bf2e7648b 100644 --- a/examples/nuxt/username-and-password/.npmrc +++ b/examples/nuxt/username-and-password/.npmrc @@ -1 +1 @@ -shamefully-hoist=true \ No newline at end of file +shamefully-hoist=true diff --git a/packages/adapter-session-unstorage/.gitignore b/packages/adapter-session-unstorage/.gitignore new file mode 100644 index 000000000..e30934148 --- /dev/null +++ b/packages/adapter-session-unstorage/.gitignore @@ -0,0 +1,4 @@ +/node_modules +/dist +.DS_Store +.env \ No newline at end of file diff --git a/packages/adapter-session-unstorage/.npmignore b/packages/adapter-session-unstorage/.npmignore new file mode 100644 index 000000000..b512c09d4 --- /dev/null +++ b/packages/adapter-session-unstorage/.npmignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/packages/adapter-session-unstorage/.prettierignore b/packages/adapter-session-unstorage/.prettierignore new file mode 100644 index 000000000..38972655f --- /dev/null +++ b/packages/adapter-session-unstorage/.prettierignore @@ -0,0 +1,13 @@ +.DS_Store +node_modules +/build +/.svelte-kit +/package +.env +.env.* +!.env.example + +# Ignore files for PNPM, NPM and YARN +pnpm-lock.yaml +package-lock.json +yarn.lock diff --git a/packages/adapter-session-unstorage/CHANGELOG.md b/packages/adapter-session-unstorage/CHANGELOG.md new file mode 100644 index 000000000..c37c93fb8 --- /dev/null +++ b/packages/adapter-session-unstorage/CHANGELOG.md @@ -0,0 +1 @@ +# @lucia-auth/adapter-session-unstorage diff --git a/packages/adapter-session-unstorage/README.md b/packages/adapter-session-unstorage/README.md new file mode 100644 index 000000000..7f5190ae6 --- /dev/null +++ b/packages/adapter-session-unstorage/README.md @@ -0,0 +1,15 @@ +# `@lucia-auth/adapter-session-storage` + +[Unstorage](https://github.com/unjs/unstorage) session adapter for Lucia version 2 beta. + +**[Documentation](https://v2.lucia-auth.com/database-adapters/unstorage)** + +**[Lucia documentation](https://v2.lucia-auth.com)** + +**[Changelog](https://github.com/pilcrowOnPaper/lucia/blob/main/packages/session-adapter-storage/CHANGELOG.md)** + +## Installation + +``` +npm install @lucia-auth/adapter-session-unstorage +``` diff --git a/packages/adapter-session-unstorage/package.json b/packages/adapter-session-unstorage/package.json new file mode 100644 index 000000000..4551666c0 --- /dev/null +++ b/packages/adapter-session-unstorage/package.json @@ -0,0 +1,45 @@ +{ + "name": "@lucia-auth/adapter-session-unstorage", + "version": "2.0.0-beta.0", + "description": "Unstorage session adapter for Lucia", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "module": "dist/index.js", + "type": "module", + "files": [ + "/dist/", + "CHANGELOG.md" + ], + "scripts": { + "build": "shx rm -rf ./dist/* && tsc", + "test": "tsx test/index.ts", + "auri.build": "pnpm build" + }, + "keywords": [ + "lucia", + "lucia-auth", + "auth", + "authentication", + "adapter", + "unstorage", + "session" + ], + "repository": { + "type": "git", + "url": "https://github.com/pilcrowOnPaper/lucia", + "directory": "packages/adapter-session-unstorage" + }, + "license": "MIT", + "exports": { + ".": "./index.js" + }, + "peerDependencies": { + "lucia": "2.0.0-beta.4", + "unstorage": "^1.6.1" + }, + "devDependencies": { + "@lucia-auth/adapter-test": "latest", + "tsx": "^3.12.6", + "lucia": "latest" + } +} diff --git a/packages/adapter-session-unstorage/src/index.ts b/packages/adapter-session-unstorage/src/index.ts new file mode 100644 index 000000000..c65af73b7 --- /dev/null +++ b/packages/adapter-session-unstorage/src/index.ts @@ -0,0 +1 @@ +export { unstorageAdapter as unstorage } from "./unstorage.js"; diff --git a/packages/adapter-session-unstorage/src/lucia.d.ts b/packages/adapter-session-unstorage/src/lucia.d.ts new file mode 100644 index 000000000..97a71b7b4 --- /dev/null +++ b/packages/adapter-session-unstorage/src/lucia.d.ts @@ -0,0 +1,6 @@ +/// +declare namespace Lucia { + type Auth = any; + type DatabaseUserAttributes = {}; + type DatabaseSessionAttributes = {}; +} diff --git a/packages/adapter-session-unstorage/src/unstorage.ts b/packages/adapter-session-unstorage/src/unstorage.ts new file mode 100644 index 000000000..df2356d2e --- /dev/null +++ b/packages/adapter-session-unstorage/src/unstorage.ts @@ -0,0 +1,80 @@ +import { prefixStorage } from "unstorage"; + +import type { SessionSchema, SessionAdapter, InitializeAdapter } from "lucia"; +import type { Storage } from "unstorage"; + +export const DEFAULT_SESSION_PREFIX = "session"; +export const DEFAULT_USER_SESSION_PREFIX = "user_session"; + +export const unstorageAdapter = ( + storage: Storage, + prefixes?: { + session: string; + userSession: string; + } +): InitializeAdapter => { + return () => { + const sessionStorage = prefixStorage( + storage, + prefixes?.session ?? DEFAULT_SESSION_PREFIX + ); + const getUserSessionStorage = (userId: string) => { + const prefix = [ + prefixes?.userSession ?? DEFAULT_USER_SESSION_PREFIX, + userId + ].join(":"); + return prefixStorage<"">(storage, prefix); + }; + + return { + getSession: async (sessionId) => { + const sessionResult = (await sessionStorage.getItem(sessionId)) ?? null; + return sessionResult; + }, + getSessionsByUserId: async (userId) => { + const userSessionStorage = getUserSessionStorage(userId); + const sessionIds = await userSessionStorage.getKeys(); + const sessionResults = await Promise.all( + sessionIds.map((sessionId) => { + return sessionStorage.getItem(sessionId); + }) + ); + return sessionResults.filter( + (sessionResult): sessionResult is SessionSchema => !!sessionResult + ); + }, + setSession: async (session) => { + const userSessionStorage = getUserSessionStorage(session.user_id); + await Promise.all([ + userSessionStorage.setItem(session.user_id, ""), + sessionStorage.setItem(session.id, session) + ]); + }, + deleteSession: async (sessionId) => { + const sessionResult = (await sessionStorage.getItem(sessionId)) ?? null; + if (!sessionResult) return; + const sessionUserStorage = getUserSessionStorage(sessionId); + await Promise.all([ + sessionStorage.removeItem(sessionId), + sessionUserStorage.removeItem(sessionId) + ]); + }, + deleteSessionsByUserId: async (userId) => { + const userSessionStorage = getUserSessionStorage(userId); + const sessionIds = await userSessionStorage.getKeys(); + await Promise.all([ + ...sessionIds.map((sessionId) => { + return sessionStorage.removeItem(sessionId); + }), + userSessionStorage.clear() + ]); + }, + updateSession: async (sessionId, partialSession) => { + const sessionResult = (await sessionStorage.getItem(sessionId)) ?? null; + if (!sessionResult) return; + const updatedSession = { ...sessionResult, ...partialSession }; + await sessionStorage.setItem(sessionId, updatedSession); + } + }; + }; +}; diff --git a/packages/adapter-session-unstorage/test/index.ts b/packages/adapter-session-unstorage/test/index.ts new file mode 100644 index 000000000..3c1b13121 --- /dev/null +++ b/packages/adapter-session-unstorage/test/index.ts @@ -0,0 +1,40 @@ +import { testSessionAdapter, Database } from "@lucia-auth/adapter-test"; +import { LuciaError } from "lucia"; +import { createStorage } from "unstorage"; +import { + unstorageAdapter, + DEFAULT_SESSION_PREFIX, + DEFAULT_USER_SESSION_PREFIX +} from "../src/unstorage.js"; + +import type { QueryHandler } from "@lucia-auth/adapter-test"; +import type { SessionSchema } from "lucia"; + +const storage = createStorage(); + +export const adapter = unstorageAdapter(storage)(LuciaError); + +export const queryHandler: QueryHandler = { + session: { + get: async () => { + const sessionIds = await storage.getKeys(DEFAULT_SESSION_PREFIX); + return Promise.all( + sessionIds.map((id) => storage.getItem(id) as Promise) + ); + }, + insert: async (session) => { + await Promise.all([ + storage.setItem(`${DEFAULT_SESSION_PREFIX}:${session.id}`, session), + storage.setItem( + [DEFAULT_USER_SESSION_PREFIX, session.user_id, session.id].join(":"), + "" + ) + ]); + }, + clear: async () => storage.clear() + } +}; + +await testSessionAdapter(adapter, new Database(queryHandler)); + +process.exit(0); diff --git a/packages/adapter-session-unstorage/tsconfig.json b/packages/adapter-session-unstorage/tsconfig.json new file mode 100644 index 000000000..0bf7d70b9 --- /dev/null +++ b/packages/adapter-session-unstorage/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "noImplicitAny": true, + "module": "es2022", + "moduleResolution": "nodenext", + "target": "es2022", + "allowSyntheticDefaultImports": true, + "declaration": true, + "outDir": "./dist", + "strict": true + }, + "include": ["src"], + "exclude": ["node_modules/", "**/__tests__/*"] +}