@absolutejs/auth 0.80.0 → 0.80.1
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/CHANGELOG.md +6 -0
- package/README.md +2 -0
- package/changelog.json +10 -0
- package/docs/PERSISTENT-CREDENTIALS.md +103 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,12 @@ This file is generated by `absolute-changelog` from the entries in
|
|
|
6
6
|
`changelog/`. Edit an entry, not this file — and add new ones under
|
|
7
7
|
`changelog/unreleased/`.
|
|
8
8
|
|
|
9
|
+
## 0.80.1 — 2026-09-22
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **Document verified persistent credentials integration and correct Drizzle and migration APIs**
|
|
14
|
+
|
|
9
15
|
## 0.80.0 — 2026-09-22
|
|
10
16
|
|
|
11
17
|
### Added
|
package/README.md
CHANGED
|
@@ -440,3 +440,5 @@ Studio's `absolute-auth setup` reads the selected adapter from
|
|
|
440
440
|
migrations and warns that sessions reset on restart. The Neon adapter requires
|
|
441
441
|
a real `DATABASE_URL` and runs migrations. Missing or unknown selections fail
|
|
442
442
|
with an actionable error; custom adapters must configure their own migrations.
|
|
443
|
+
|
|
444
|
+
For a complete credentials-only Bun setup, see [Persistent email/password sign-in](docs/PERSISTENT-CREDENTIALS.md). It includes real migration and auth configuration APIs, durable user records, and driver settings.
|
package/changelog.json
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
"contract": 1,
|
|
3
3
|
"name": "@absolutejs/auth",
|
|
4
4
|
"releases": [
|
|
5
|
+
{
|
|
6
|
+
"changes": [
|
|
7
|
+
{
|
|
8
|
+
"kind": "fixed",
|
|
9
|
+
"summary": "Document verified persistent credentials integration and correct Drizzle and migration APIs"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"date": "2026-09-22",
|
|
13
|
+
"version": "0.80.1"
|
|
14
|
+
},
|
|
5
15
|
{
|
|
6
16
|
"changes": [
|
|
7
17
|
{
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Persistent email/password sign-in (Bun + Elysia 2)
|
|
2
|
+
|
|
3
|
+
Use this credentials-only setup instead of the social-login manifest recipe.
|
|
4
|
+
The same API pattern passed synthetic browser signup, login, logout, booking and
|
|
5
|
+
full-container restart tests in Studio using auth 0.80.0. The example deliberately
|
|
6
|
+
sends no email and refuses production. For a real business configure email
|
|
7
|
+
verification, delivery, password policy, backups and the application's user schema.
|
|
8
|
+
|
|
9
|
+
Install published auth and a compatible Drizzle release. Set AUTH_DATABASE_URL
|
|
10
|
+
securely to a real isolated PostgreSQL database; never substitute a placeholder.
|
|
11
|
+
The application owns user records. The auth package owns credentials and sessions.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { auth } from '@absolutejs/auth/server';
|
|
15
|
+
import { createPostgresAuthSessionStore, createPostgresCredentialStore, runMigrations } from '@absolutejs/auth';
|
|
16
|
+
import { SQL } from 'bun';
|
|
17
|
+
import { Database } from 'bun:sqlite';
|
|
18
|
+
import { drizzle } from 'drizzle-orm/bun-sql';
|
|
19
|
+
import { mkdirSync } from 'node:fs';
|
|
20
|
+
|
|
21
|
+
if (process.env.NODE_ENV === 'production') throw new Error('Synthetic example: configure verified email and real business policy before production');
|
|
22
|
+
type Customer = { id: string; email: string };
|
|
23
|
+
const databaseUrl = process.env.AUTH_DATABASE_URL;
|
|
24
|
+
if (!databaseUrl)
|
|
25
|
+
throw new Error(
|
|
26
|
+
"AUTH_DATABASE_URL must point to the isolated acceptance database",
|
|
27
|
+
);
|
|
28
|
+
const migrationClient = new SQL({ url: databaseUrl, max: 1, prepare: false });
|
|
29
|
+
try {
|
|
30
|
+
await runMigrations({
|
|
31
|
+
blocks: ["sessions", "credentials"],
|
|
32
|
+
client: {
|
|
33
|
+
query: async (text, values) => ({
|
|
34
|
+
rows: await migrationClient.unsafe(text, values ? [...values] : []),
|
|
35
|
+
}),
|
|
36
|
+
},
|
|
37
|
+
log: () => undefined,
|
|
38
|
+
});
|
|
39
|
+
} finally {
|
|
40
|
+
await migrationClient.close();
|
|
41
|
+
}
|
|
42
|
+
const authDb = drizzle({ client: new SQL(databaseUrl) });
|
|
43
|
+
const decodeCustomer = (value: unknown): Customer => {
|
|
44
|
+
if (typeof value !== "object" || value === null)
|
|
45
|
+
throw new Error("Invalid customer session");
|
|
46
|
+
const id: unknown = Reflect.get(value, "id");
|
|
47
|
+
const email: unknown = Reflect.get(value, "email");
|
|
48
|
+
if (typeof id !== "string" || typeof email !== "string")
|
|
49
|
+
throw new Error("Invalid customer session");
|
|
50
|
+
return { id, email };
|
|
51
|
+
};
|
|
52
|
+
mkdirSync(".data", { recursive: true });
|
|
53
|
+
const db = new Database(".data/customer-example.sqlite", { create: true });
|
|
54
|
+
db.exec(
|
|
55
|
+
"PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000; CREATE TABLE IF NOT EXISTS customers(id TEXT PRIMARY KEY, email TEXT NOT NULL UNIQUE);",
|
|
56
|
+
);
|
|
57
|
+
const userById = db.query<Customer, [string]>(
|
|
58
|
+
"SELECT id,email FROM customers WHERE id=?",
|
|
59
|
+
);
|
|
60
|
+
const userByEmail = db.query<Customer, [string]>(
|
|
61
|
+
"SELECT id,email FROM customers WHERE email=?",
|
|
62
|
+
);
|
|
63
|
+
const insertUser = db.query("INSERT INTO customers(id,email) VALUES(?,?)");
|
|
64
|
+
const sessions = createPostgresAuthSessionStore(authDb, decodeCustomer);
|
|
65
|
+
export const authentication = await auth<Customer>({
|
|
66
|
+
providersConfiguration: {},
|
|
67
|
+
getUser: (id) => userById.get(String(id)) ?? null,
|
|
68
|
+
authSessionStore: sessions,
|
|
69
|
+
cookieSecure: true,
|
|
70
|
+
credentials: {
|
|
71
|
+
credentialStore: createPostgresCredentialStore(authDb),
|
|
72
|
+
getUserByEmail: (email) => userByEmail.get(email) ?? null,
|
|
73
|
+
onCreateCredentialUser: ({ email }) => {
|
|
74
|
+
const user = { id: crypto.randomUUID(), email };
|
|
75
|
+
insertUser.run(user.id, email);
|
|
76
|
+
return user;
|
|
77
|
+
},
|
|
78
|
+
// Local synthetic mode deliberately delivers no messages.
|
|
79
|
+
onSendEmail: () => undefined,
|
|
80
|
+
requireEmailVerification: false,
|
|
81
|
+
passwordPolicy: { minLength: 12, checkBreaches: false },
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Important API details:
|
|
88
|
+
|
|
89
|
+
- `auth` is asynchronous and comes from `@absolutejs/auth/server`.
|
|
90
|
+
- `authSessionStore` is top-level. `credentialStore` and credential callbacks
|
|
91
|
+
belong inside `credentials`. There is no top-level `sessionStore` option.
|
|
92
|
+
- Stores do not have `.runMigrations()`. Import `runMigrations` from the package
|
|
93
|
+
root and supply its query client as shown above.
|
|
94
|
+
- With Drizzle 1's Bun driver use `drizzle({ client })`, not `drizzle(client)`.
|
|
95
|
+
- Keep `prepare: false` on the migration client only. Application queries need
|
|
96
|
+
Bun's default prepared mode to encode JSON session data correctly.
|
|
97
|
+
- The built-in sign-out route is `DELETE /oauth2/signout`.
|
|
98
|
+
- Use `createAuthContext<Customer>()` from `/server` to read typed authenticated
|
|
99
|
+
users. Business APIs must check the session server-side and scope records to
|
|
100
|
+
that user. Do not implement a second password or cookie system.
|
|
101
|
+
- Preserve cookie headers when exposing a narrow typed JSON wrapper around the
|
|
102
|
+
package's configurable credential routes. Never store session tokens in browser
|
|
103
|
+
localStorage. Browser business requests belong in typed Eden + React Query.
|
package/package.json
CHANGED