@mostajs/auth-lite 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ GNU AFFERO GENERAL PUBLIC LICENSE
2
+ Version 3, 19 November 2007
3
+
4
+ Copyright (c) 2026 Dr Hamid MADANI <drmdh@msn.com>
5
+
6
+ This program is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU Affero General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ This program is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU Affero General Public License for more details.
15
+
16
+ You should have received a copy of the GNU Affero General Public License
17
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
18
+
19
+ COMMERCIAL LICENSE
20
+
21
+ For organizations that cannot comply with the AGPL open-source requirements,
22
+ a commercial license is available. Contact: drmdh@msn.com
23
+
24
+ The commercial license allows you to:
25
+ - Use the software in proprietary/closed-source projects
26
+ - Modify without publishing your source code
27
+ - Get priority support and SLA
28
+
29
+ Contact: Dr Hamid MADANI <drmdh@msn.com>
package/README.md ADDED
@@ -0,0 +1,212 @@
1
+ # @mostajs/auth-lite
2
+
3
+ > Minimal email/password + session auth for **Next.js (App Router)** on top of [`@mostajs/orm`](https://www.npmjs.com/package/@mostajs/orm).
4
+ > **No native addon** (no bcrypt/argon2) → it boots in **Bolt.new / StackBlitz / WebContainers / the edge**, on the first try.
5
+
6
+ [![npm](https://img.shields.io/npm/v/@mostajs/auth-lite)](https://www.npmjs.com/package/@mostajs/auth-lite)
7
+ [![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](./LICENSE)
8
+
9
+ `auth-lite` is the **readable, dependency-free** auth brick: salted iterated SHA-256
10
+ password hashing (Node core `crypto`), a ready-made `Session` schema, login/signup/logout
11
+ Route Handlers, and a `getCurrentUser()` for Server Components. It was extracted from the
12
+ `mostajs-saas-starter` and hardened until it actually boots inside the StackBlitz
13
+ WebContainer — the lessons learned are baked into its API (see [Why "lite"](#why-lite--webcontainer-safe-by-design)).
14
+
15
+ > **Need OAuth, MFA, WebAuthn, magic links, Argon2id, refresh tokens?** Use the full
16
+ > [`@mostajs/auth`](https://www.npmjs.com/package/@mostajs/auth) instead. `auth-lite` and
17
+ > `@mostajs/auth` are alternatives — pick one (see [comparison](#auth-lite-vs-mostajsauth)).
18
+
19
+ ---
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ npm i @mostajs/auth-lite @mostajs/orm next
25
+ ```
26
+
27
+ Peer requirements: `@mostajs/orm >= 2.5.2`, `next >= 14`.
28
+ To boot in a browser / WebContainer, use one of the ORM's WASM dialects (`sqljs` or `pglite`)
29
+ so there is **zero native binary** in the dependency tree.
30
+
31
+ ---
32
+
33
+ ## Quickstart (5 steps)
34
+
35
+ ### 1 · Schemas — your `User` + the bundled `Session`
36
+
37
+ Your `User` entity must have at least `email` (unique), `passwordHash`, and `name`.
38
+
39
+ ```ts
40
+ // lib/orm/schemas.ts
41
+ import type { EntitySchema } from '@mostajs/orm';
42
+ export { SessionSchema } from '@mostajs/auth-lite';
43
+
44
+ export const UserSchema: EntitySchema = {
45
+ name: 'User',
46
+ collection: 'users',
47
+ fields: {
48
+ email: { type: 'string', required: true, unique: true, lowercase: true, trim: true },
49
+ name: { type: 'string', required: true, trim: true },
50
+ passwordHash: { type: 'string', required: true },
51
+ },
52
+ indexes: [{ fields: ['email'], unique: true }],
53
+ timestamps: true,
54
+ };
55
+ ```
56
+
57
+ ### 2 · Repos — expose `getRepos()`
58
+
59
+ ```ts
60
+ // lib/orm/index.ts
61
+ import { BaseRepository } from '@mostajs/orm';
62
+ import { getDialect } from '@mostajs/orm';
63
+ import { SessionSchema } from '@mostajs/auth-lite';
64
+ import { UserSchema } from './schemas';
65
+
66
+ export type User = { id: string; email: string; name: string; passwordHash: string };
67
+
68
+ export async function getRepos() {
69
+ const dialect = await getDialect(
70
+ { dialect: 'sqljs', uri: ':memory:' }, // WASM → boots in Bolt/StackBlitz
71
+ [UserSchema, SessionSchema],
72
+ );
73
+ return {
74
+ users: new BaseRepository<User>(UserSchema, dialect),
75
+ sessions: new BaseRepository(SessionSchema, dialect),
76
+ };
77
+ }
78
+ ```
79
+
80
+ ### 3 · Route Handlers — login / signup / logout
81
+
82
+ ```ts
83
+ // app/api/auth/login/route.ts
84
+ import { createAuthHandlers } from '@mostajs/auth-lite';
85
+ import { getRepos } from '@/lib/orm';
86
+ export const POST = createAuthHandlers({ getRepos }).login;
87
+ ```
88
+
89
+ ```ts
90
+ // app/api/auth/signup/route.ts
91
+ import { createAuthHandlers } from '@mostajs/auth-lite';
92
+ import { getRepos } from '@/lib/orm';
93
+ export const POST = createAuthHandlers({ getRepos }).signup;
94
+ ```
95
+
96
+ ```ts
97
+ // app/api/auth/logout/route.ts
98
+ import { createAuthHandlers } from '@mostajs/auth-lite';
99
+ import { getRepos } from '@/lib/orm';
100
+ export const POST = createAuthHandlers({ getRepos }).logout;
101
+ ```
102
+
103
+ ### 4 · Read the session in Server Components
104
+
105
+ ```ts
106
+ // lib/auth.ts
107
+ import { createGetCurrentUser } from '@mostajs/auth-lite';
108
+ import { getRepos, type User } from '@/lib/orm';
109
+ export const getCurrentUser = createGetCurrentUser<User>({ getRepos });
110
+ ```
111
+
112
+ ### 5 · Forms post to the handlers (works without client JS)
113
+
114
+ ```tsx
115
+ // app/login/page.tsx
116
+ export default function LoginPage() {
117
+ return (
118
+ <form action="/api/auth/login" method="post">
119
+ <input name="email" type="email" required />
120
+ <input name="password" type="password" required />
121
+ <button type="submit">Log in</button>
122
+ </form>
123
+ );
124
+ }
125
+ ```
126
+
127
+ Guard each protected page:
128
+
129
+ ```tsx
130
+ // app/dashboard/page.tsx
131
+ import { redirect } from 'next/navigation';
132
+ import { getCurrentUser } from '@/lib/auth';
133
+
134
+ export const dynamic = 'force-dynamic';
135
+
136
+ export default async function Dashboard() {
137
+ const user = await getCurrentUser();
138
+ if (!user) redirect('/login'); // ← per page, not only in the layout
139
+ return <p>Welcome, {user.name}</p>;
140
+ }
141
+ ```
142
+
143
+ ---
144
+
145
+ ## API
146
+
147
+ | Export | Signature | Purpose |
148
+ |---|---|---|
149
+ | `hashPassword` | `(password: string) => string` | Salted, 10 000× iterated SHA-256 → `"salt:hashHex"`. No native addon. |
150
+ | `verifyPassword` | `(password: string, stored: string) => boolean` | Constant-time check (`timingSafeEqual`). |
151
+ | `SessionSchema` | `EntitySchema` | `Session` entity (`token` unique, `expiresAt`, `user` → `User` M-1 cascade). Register it alongside `User`. |
152
+ | `createAuthHandlers` | `(config) => { login, signup, logout }` | Each is `(req: NextRequest) => Promise<NextResponse>`; export as `POST`. |
153
+ | `createGetCurrentUser` | `<TUser>(config) => () => Promise<TUser \| null>` | Resolve the logged-in user from the cookie (read before any DB call). |
154
+
155
+ ### `AuthLiteConfig`
156
+
157
+ ```ts
158
+ interface AuthLiteConfig {
159
+ getRepos: () => Promise<{ users: AuthRepo; sessions: AuthRepo }>; // required
160
+ cookieName?: string; // default "session"
161
+ ttlDays?: number; // default 7
162
+ afterAuth?: string; // default "/dashboard"
163
+ afterLogout?: string; // default "/"
164
+ loginErrorPath?: string; // default "/login?error=invalid"
165
+ signupErrorPath?: (kind: 'invalid' | 'exists') => string; // default "/signup?error=<kind>"
166
+ }
167
+ ```
168
+
169
+ `AuthRepo` is the minimal subset this module needs (`findOne`, `create`, `delete`,
170
+ `findByIdWithRelations`) — fully satisfied by `@mostajs/orm`'s `BaseRepository`.
171
+
172
+ ---
173
+
174
+ ## Why "lite" — WebContainer-safe by design
175
+
176
+ Tested for real in StackBlitz; the failures it hit are now prevented by the API itself:
177
+
178
+ - **Cookies via Route Handlers, not `cookies()`.** Some runtimes (StackBlitz WebContainer)
179
+ lose Next's request async-context (AsyncLocalStorage) across an `await` on the DB, which
180
+ makes a later `cookies()` throw *"called outside a request scope"*. `createAuthHandlers`
181
+ sets the cookie on the **`NextResponse`** object (`res.cookies.set`) — that works everywhere.
182
+ - **Read the cookie before the DB.** `getCurrentUser` reads the cookie while the request
183
+ context is still intact, then resolves the session.
184
+ - **No native binary.** SHA-256 from Node core `crypto` — no bcrypt/argon2 to compile. For a
185
+ classic production server you can swap in argon2/scrypt (the `hashPassword`/`verifyPassword`
186
+ API is unchanged) or move to `@mostajs/auth`.
187
+ - **Guard per page.** Always `if (!user) redirect('/login')` in each protected page — a layout
188
+ guard alone is not enough (layout and page run in parallel → `null.id` crash).
189
+
190
+ ---
191
+
192
+ ## `auth-lite` vs `@mostajs/auth`
193
+
194
+ | | `@mostajs/auth-lite` | `@mostajs/auth` |
195
+ |---|---|---|
196
+ | Password hash | SHA-256 iterated (core crypto) | Argon2id |
197
+ | Methods | email/password + sessions | email/password, OAuth/OIDC, magic link, MFA TOTP, WebAuthn/Passkeys |
198
+ | Stack | `@mostajs/orm` + Next Route Handlers | NextAuth v5 + `@mostajs/rbac` |
199
+ | Native deps | **none** (WebContainer/edge ready) | argon2 etc. (server) |
200
+ | Footprint | one file, readable | ~30 sub-paths, full-featured |
201
+ | Best for | starters, MVPs, Bolt/StackBlitz demos | production apps needing rich auth |
202
+
203
+ They are **alternatives** — depend on one, not both.
204
+
205
+ ---
206
+
207
+ ## License
208
+
209
+ AGPL-3.0-or-later. A commercial license is available for proprietary/closed-source use —
210
+ see [`LICENSE`](./LICENSE).
211
+
212
+ **Author**: Dr Hamid MADANI <drmdh@msn.com>
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @mostajs/auth-lite — minimal, dependency-free email/password + session auth
3
+ * for Next.js (App Router) on top of @mostajs/orm.
4
+ *
5
+ * Why "lite": it boots in WebContainers (Bolt.new / StackBlitz) and the edge —
6
+ * no native addon (no bcrypt/argon2), and it sets the session cookie on the
7
+ * NextResponse object inside Route Handlers, which sidesteps the AsyncLocalStorage
8
+ * pitfalls of `cookies()` after a DB call in constrained runtimes.
9
+ *
10
+ * Password hashing is salted, iterated SHA-256 (works everywhere). For a
11
+ * production server you can swap in argon2/scrypt — the API is unchanged.
12
+ *
13
+ * @author Dr Hamid MADANI <drmdh@msn.com>
14
+ */
15
+ import type { NextRequest } from 'next/server';
16
+ import type { EntitySchema } from '@mostajs/orm';
17
+ export declare function hashPassword(password: string): string;
18
+ export declare function verifyPassword(password: string, stored: string): boolean;
19
+ export declare const SessionSchema: EntitySchema;
20
+ /** Minimal repository shape this module needs (compatible with @mostajs/orm BaseRepository). */
21
+ export interface AuthRepo {
22
+ findOne(filter: Record<string, unknown>): Promise<any>;
23
+ create(data: Record<string, unknown>): Promise<any>;
24
+ delete(id: string): Promise<unknown>;
25
+ findByIdWithRelations(id: string, relations: string[], options?: unknown): Promise<any>;
26
+ }
27
+ export interface AuthLiteConfig {
28
+ /** Returns the User + Session repositories (e.g. your getRepos()). */
29
+ getRepos: () => Promise<{
30
+ users: AuthRepo;
31
+ sessions: AuthRepo;
32
+ }>;
33
+ /** Session cookie name (default "session"). */
34
+ cookieName?: string;
35
+ /** Session lifetime in days (default 7). */
36
+ ttlDays?: number;
37
+ /** Where to go after login/signup (default "/dashboard"). */
38
+ afterAuth?: string;
39
+ /** Where to go after logout (default "/"). */
40
+ afterLogout?: string;
41
+ /** Redirect on invalid login (default "/login?error=invalid"). */
42
+ loginErrorPath?: string;
43
+ /** Redirect on signup error (default "/signup?error=<kind>"). */
44
+ signupErrorPath?: (kind: 'invalid' | 'exists') => string;
45
+ }
46
+ export declare function createAuthHandlers(config: AuthLiteConfig): {
47
+ login: (req: NextRequest) => Promise<import("next/server").NextResponse<unknown>>;
48
+ signup: (req: NextRequest) => Promise<import("next/server").NextResponse<unknown>>;
49
+ logout: (req: NextRequest) => Promise<import("next/server").NextResponse<unknown>>;
50
+ };
51
+ export declare function createGetCurrentUser<TUser = unknown>(config: AuthLiteConfig): () => Promise<TUser | null>;
package/dist/index.js ADDED
@@ -0,0 +1,136 @@
1
+ import { createHash, randomBytes, timingSafeEqual } from 'crypto';
2
+ import { baseFromHeaders } from '@mostajs/url';
3
+ /**
4
+ * Base PUBLIQUE (`proto://host`) pour les redirects. En WebContainer
5
+ * (StackBlitz/Bolt) ou derrière un reverse proxy, `req.url` côté Node pointe
6
+ * sur le bind interne (`http://localhost:3000`) → l'utilisateur serait redirigé
7
+ * vers localhost. `baseFromHeaders` lit l'hôte public réel depuis
8
+ * `X-Forwarded-Host`/`Host` ; fallback sur l'origine de `req.url`.
9
+ */
10
+ function reqBase(req) {
11
+ return baseFromHeaders(req.headers) ?? new URL(req.url).origin;
12
+ }
13
+ // ---------------------------------------------------------------------------
14
+ // Password hashing — salted, iterated SHA-256 (no native addon, boots anywhere)
15
+ // ---------------------------------------------------------------------------
16
+ const ITERATIONS = 10_000;
17
+ function derive(password, salt) {
18
+ let h = createHash('sha256').update(`${salt}:${password}`).digest();
19
+ for (let i = 0; i < ITERATIONS; i++)
20
+ h = createHash('sha256').update(h).digest();
21
+ return h;
22
+ }
23
+ export function hashPassword(password) {
24
+ const salt = randomBytes(16).toString('hex');
25
+ return `${salt}:${derive(password, salt).toString('hex')}`;
26
+ }
27
+ export function verifyPassword(password, stored) {
28
+ const [salt, hashHex] = stored.split(':');
29
+ if (!salt || !hashHex)
30
+ return false;
31
+ const expected = Buffer.from(hashHex, 'hex');
32
+ const actual = derive(password, salt);
33
+ return expected.length === actual.length && timingSafeEqual(expected, actual);
34
+ }
35
+ // ---------------------------------------------------------------------------
36
+ // Default Session EntitySchema — register it alongside your own `User` entity
37
+ // (User must have at least `email` (unique) and `passwordHash`; `name` for signup).
38
+ // ---------------------------------------------------------------------------
39
+ export const SessionSchema = {
40
+ name: 'Session',
41
+ collection: 'sessions',
42
+ fields: {
43
+ token: { type: 'string', required: true, unique: true },
44
+ expiresAt: { type: 'date', required: true },
45
+ },
46
+ relations: {
47
+ user: { target: 'User', type: 'many-to-one', required: true, onDelete: 'cascade' },
48
+ },
49
+ indexes: [{ fields: ['token'], unique: true }],
50
+ timestamps: true,
51
+ };
52
+ // ---------------------------------------------------------------------------
53
+ // Route Handlers — login / signup / logout (set the cookie on the response)
54
+ // ---------------------------------------------------------------------------
55
+ export function createAuthHandlers(config) {
56
+ const cookie = config.cookieName ?? 'session';
57
+ const ttlMs = (config.ttlDays ?? 7) * 86400000;
58
+ const afterAuth = config.afterAuth ?? '/dashboard';
59
+ const afterLogout = config.afterLogout ?? '/';
60
+ const loginError = config.loginErrorPath ?? '/login?error=invalid';
61
+ const signupError = config.signupErrorPath ?? ((k) => `/signup?error=${k}`);
62
+ async function startSession(req, sessions, userId) {
63
+ const { NextResponse } = await import('next/server');
64
+ const token = randomBytes(32).toString('hex');
65
+ const expiresAt = new Date(Date.now() + ttlMs);
66
+ await sessions.create({ token, user: userId, expiresAt });
67
+ const res = NextResponse.redirect(new URL(afterAuth, reqBase(req)), 303);
68
+ res.cookies.set(cookie, token, { httpOnly: true, sameSite: 'lax', path: '/', expires: expiresAt });
69
+ return res;
70
+ }
71
+ /** POST handler — verify credentials, start a session. */
72
+ async function login(req) {
73
+ const { NextResponse } = await import('next/server');
74
+ const form = await req.formData();
75
+ const email = String(form.get('email') ?? '').toLowerCase().trim();
76
+ const password = String(form.get('password') ?? '');
77
+ const { users, sessions } = await config.getRepos();
78
+ const user = await users.findOne({ email });
79
+ if (!user || !verifyPassword(password, user.passwordHash)) {
80
+ return NextResponse.redirect(new URL(loginError, reqBase(req)), 303);
81
+ }
82
+ return startSession(req, sessions, user.id);
83
+ }
84
+ /** POST handler — create the account, start a session. */
85
+ async function signup(req) {
86
+ const { NextResponse } = await import('next/server');
87
+ const form = await req.formData();
88
+ const email = String(form.get('email') ?? '').toLowerCase().trim();
89
+ const name = String(form.get('name') ?? '').trim();
90
+ const password = String(form.get('password') ?? '');
91
+ if (!email || !name || password.length < 6) {
92
+ return NextResponse.redirect(new URL(signupError('invalid'), reqBase(req)), 303);
93
+ }
94
+ const { users, sessions } = await config.getRepos();
95
+ if (await users.findOne({ email })) {
96
+ return NextResponse.redirect(new URL(signupError('exists'), reqBase(req)), 303);
97
+ }
98
+ const user = await users.create({ email, name, passwordHash: hashPassword(password) });
99
+ return startSession(req, sessions, user.id);
100
+ }
101
+ /** POST handler — destroy the session (DB + cookie). */
102
+ async function logout(req) {
103
+ const { NextResponse } = await import('next/server');
104
+ const token = req.cookies.get(cookie)?.value;
105
+ if (token) {
106
+ const { sessions } = await config.getRepos();
107
+ const session = await sessions.findOne({ token });
108
+ if (session)
109
+ await sessions.delete(session.id);
110
+ }
111
+ const res = NextResponse.redirect(new URL(afterLogout, reqBase(req)), 303);
112
+ res.cookies.delete(cookie);
113
+ return res;
114
+ }
115
+ return { login, signup, logout };
116
+ }
117
+ // ---------------------------------------------------------------------------
118
+ // getCurrentUser — read the session in Server Components (cookie read before DB)
119
+ // ---------------------------------------------------------------------------
120
+ export function createGetCurrentUser(config) {
121
+ const cookie = config.cookieName ?? 'session';
122
+ return async function getCurrentUser() {
123
+ const { cookies } = await import('next/headers');
124
+ const token = (await cookies()).get(cookie)?.value;
125
+ if (!token)
126
+ return null;
127
+ const { sessions } = await config.getRepos();
128
+ const session = await sessions.findOne({ token });
129
+ if (!session)
130
+ return null;
131
+ if (new Date(session.expiresAt) < new Date())
132
+ return null;
133
+ const populated = (await sessions.findByIdWithRelations(session.id, ['user']));
134
+ return populated?.user ?? null;
135
+ };
136
+ }
package/llms.txt ADDED
@@ -0,0 +1,101 @@
1
+ # @mostajs/auth-lite — fiche LLM
2
+ > Auth email/mot de passe + sessions minimale pour Next.js (App Router) sur @mostajs/orm. Zéro addon natif → boote dans Bolt.new / StackBlitz / edge.
3
+
4
+ - Version: 0.1.0 · Licence: AGPL-3.0-or-later · Auteur: Dr Hamid MADANI <drmdh@msn.com>
5
+ - Chemin: mostajs/mosta-auth-lite · Statut: scaffold initial (src/index.ts) · peer: @mostajs/orm >=2.5.2, next >=14
6
+ - Origine: extraction de la couche auth du `mostajs-saas-starter`, durcie jusqu'à booter réellement dans le WebContainer StackBlitz/Bolt.
7
+
8
+ ## RÔLE
9
+ Brique d'authentification **minimale et sans dépendance native** pour les apps Next.js
10
+ App Router qui tournent sur `@mostajs/orm` — en particulier celles générées/exécutées
11
+ dans un WebContainer (Bolt.new, StackBlitz) ou à l'edge, où `bcrypt`/`argon2` (addons
12
+ natifs) ne se compilent pas. Fournit : hachage de mot de passe (SHA-256 salé itéré,
13
+ cœur Node, zéro binaire), un `EntitySchema` de session prêt à l'emploi, des Route
14
+ Handlers login/signup/logout, et un lecteur de session pour Server Components.
15
+ Ce N'EST PAS un remplaçant de `@mostajs/auth` (le module complet : Argon2id, OAuth/OIDC,
16
+ magic link, MFA TOTP, WebAuthn, refresh tokens, NextAuth v5). `auth-lite` vise la
17
+ simplicité lisible et la compatibilité WebContainer ; `@mostajs/auth` vise la production
18
+ riche en fonctionnalités. Choisir l'un OU l'autre selon le besoin.
19
+
20
+ ## INSTALLATION
21
+ npm i @mostajs/auth-lite @mostajs/orm next
22
+ (le consumer fournit son entité `User` et ses repos via @mostajs/orm ; le module ne ship
23
+ aucune DB. Pour booter en navigateur/WebContainer, utiliser un dialecte WASM de l'ORM :
24
+ `sqljs` ou `pglite`.)
25
+
26
+ ## EXPORTS (point d'entrée unique `.`)
27
+ - Fonctions: hashPassword, verifyPassword, createAuthHandlers, createGetCurrentUser
28
+ - Schéma: SessionSchema (EntitySchema)
29
+ - Types: AuthRepo, AuthLiteConfig
30
+
31
+ ## API — SIGNATURES
32
+ hashPassword(password: string): string
33
+ // → "salt:hashHex" ; SHA-256 salé + itéré 10 000× (crypto cœur Node, aucun addon natif).
34
+ verifyPassword(password: string, stored: string): boolean
35
+ // comparaison à temps constant (timingSafeEqual). false si format invalide.
36
+ SessionSchema: EntitySchema
37
+ // name 'Session', collection 'sessions' ; fields { token (string, unique), expiresAt (date) } ;
38
+ // relation user → 'User' (many-to-one, required, onDelete: 'cascade') ; index unique sur token ; timestamps.
39
+ createAuthHandlers(config: AuthLiteConfig): { login, signup, logout }
40
+ // chaque membre = (req: NextRequest) => Promise<NextResponse> — à exporter comme POST dans un Route Handler.
41
+ // login : vérifie email+password, crée la session, pose le cookie sur la réponse, 303 → afterAuth.
42
+ // signup : valide (email/name requis, password ≥ 6), rejette si email existe, crée user+session, 303 → afterAuth.
43
+ // logout : supprime la session DB + le cookie, 303 → afterLogout.
44
+ createGetCurrentUser<TUser = unknown>(config: AuthLiteConfig): () => Promise<TUser | null>
45
+ // getCurrentUser() : lit le cookie AVANT toute requête DB, résout la session, renvoie l'utilisateur peuplé ou null.
46
+ // null si pas de cookie / session absente / session expirée.
47
+
48
+ ## TYPES
49
+ AuthRepo {
50
+ findOne(filter): Promise<any>
51
+ create(data): Promise<any>
52
+ delete(id: string): Promise<unknown>
53
+ findByIdWithRelations(id: string, relations: string[], options?): Promise<any>
54
+ } // sous-ensemble compatible @mostajs/orm BaseRepository.
55
+
56
+ AuthLiteConfig {
57
+ getRepos: () => Promise<{ users: AuthRepo; sessions: AuthRepo }> // requis
58
+ cookieName?: string // défaut "session"
59
+ ttlDays?: number // défaut 7
60
+ afterAuth?: string // défaut "/dashboard"
61
+ afterLogout?: string // défaut "/"
62
+ loginErrorPath?: string // défaut "/login?error=invalid"
63
+ signupErrorPath?: (kind: 'invalid' | 'exists') => string // défaut "/signup?error=<kind>"
64
+ }
65
+
66
+ ## CONTRAINTES CONSUMER
67
+ - L'entité `User` doit exposer au minimum `email` (unique), `passwordHash`, et `name` (pour signup).
68
+ - Enregistrer `SessionSchema` à côté de l'entité `User` dans l'ORM.
69
+ - `getRepos()` renvoie des `BaseRepository` (ou objets compatibles `AuthRepo`).
70
+
71
+ ## POURQUOI « lite » — leçons WebContainer (gravées dans l'API)
72
+ - L1 — Mutation de cookie via Route Handlers : `createAuthHandlers` pose le cookie sur l'objet
73
+ `NextResponse` (`res.cookies.set`), pas via `cookies()`. Certains runtimes (StackBlitz
74
+ WebContainer) perdent le contexte async (AsyncLocalStorage) de Next à travers un `await` DB,
75
+ ce qui fait planter `cookies()` ("called outside a request scope"). Poser le cookie sur la
76
+ réponse marche partout.
77
+ - L2 — Lecture de session avant DB : `getCurrentUser` lit le cookie AVANT toute requête DB
78
+ (contexte requête intact), puis seulement résout l'utilisateur.
79
+ - L3 — Zéro addon natif : hachage SHA-256 (cœur `crypto`), pas de bcrypt/argon2 → compile et
80
+ boote en WebContainer / edge. Pour un serveur prod classique, on peut swapper argon2/scrypt
81
+ (l'API hashPassword/verifyPassword reste identique) ou passer à `@mostajs/auth`.
82
+ - Garde-fou consumer : protéger CHAQUE page par `const u = await getCurrentUser(); if (!u) redirect('/login')`
83
+ — ne pas se reposer sur le seul `layout` + assertion `!` (layout et page s'exécutent en
84
+ parallèle → crash `null.id`).
85
+
86
+ ## EXEMPLE MINIMAL (Route Handler login)
87
+ // app/api/auth/login/route.ts
88
+ import { createAuthHandlers } from '@mostajs/auth-lite';
89
+ import { getRepos } from '@/lib/orm';
90
+ const { login } = createAuthHandlers({ getRepos });
91
+ export const POST = login;
92
+
93
+ // lib/auth.ts (Server Components)
94
+ import { createGetCurrentUser } from '@mostajs/auth-lite';
95
+ import { getRepos } from '@/lib/orm';
96
+ export const getCurrentUser = createGetCurrentUser<User>({ getRepos });
97
+
98
+ ## VOIR AUSSI
99
+ - `@mostajs/orm` — la couche données (dialectes WASM `sqljs`/`pglite` pour WebContainer).
100
+ - `@mostajs/auth` — auth complète (Argon2id, OAuth, MFA, WebAuthn…) pour serveurs prod.
101
+ - `mostajs-saas-starter` — app de référence d'où la couche est extraite.
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@mostajs/auth-lite",
3
+ "version": "0.1.0",
4
+ "description": "Minimal email/password + session auth for Next.js on @mostajs/orm. No native addon — boots in Bolt.new / StackBlitz / edge.",
5
+ "license": "AGPL-3.0-or-later",
6
+ "author": "Dr Hamid MADANI <drmdh@msn.com>",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "llms.txt",
19
+ "LICENSE"
20
+ ],
21
+ "keywords": [
22
+ "auth",
23
+ "authentication",
24
+ "sessions",
25
+ "nextjs",
26
+ "mostajs",
27
+ "orm",
28
+ "webcontainer",
29
+ "lightweight",
30
+ "password"
31
+ ],
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/apolocine/mosta-auth-lite"
35
+ },
36
+ "homepage": "https://mostajs.dev",
37
+ "engines": {
38
+ "node": ">=18.18.0"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc",
42
+ "dev": "tsc --watch",
43
+ "prepublishOnly": "npm run build"
44
+ },
45
+ "peerDependencies": {
46
+ "@mostajs/orm": ">=2.5.2",
47
+ "next": ">=14.0.0"
48
+ },
49
+ "devDependencies": {
50
+ "@mostajs/orm": "^2.5.2",
51
+ "@types/node": "^22.0.0",
52
+ "better-sqlite3": "^12.10.0",
53
+ "next": "^15.4.11",
54
+ "react": "^19.0.0",
55
+ "typescript": "^5.6.0"
56
+ },
57
+ "funding": {
58
+ "type": "github",
59
+ "url": "https://github.com/sponsors/apolocine"
60
+ },
61
+ "dependencies": {
62
+ "@mostajs/url": "^0.5.0"
63
+ }
64
+ }