sentri 1.0.3 → 1.0.5

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.
@@ -0,0 +1,160 @@
1
+ // sentri — Drizzle adapter template
2
+ // Generated by: npx sentri generate drizzle
3
+ //
4
+ // Adjust the import paths to match your project structure.
5
+ // This template assumes tables: users, roles, userRoles, sessions.
6
+ // Column names follow the sentri Drizzle schema convention.
7
+ //
8
+ // Replace NodePgDatabase with the type that matches your Drizzle driver:
9
+ // import { type NodePgDatabase } from 'drizzle-orm/node-postgres'; (default)
10
+ // import { type LibSQLDatabase } from 'drizzle-orm/libsql';
11
+ // import { type MySql2Database } from 'drizzle-orm/mysql2';
12
+
13
+ import { eq } from 'drizzle-orm';
14
+ import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
15
+ import type { AuthAdapter } from 'sentri';
16
+ import { users, roles, userRoles, sessions } from './schema.js';
17
+
18
+ class AdapterConfigError extends Error {
19
+ constructor(message: string) {
20
+ super(message);
21
+ this.name = 'AdapterConfigError';
22
+ }
23
+ }
24
+
25
+ export function createAdapter(db: NodePgDatabase): AuthAdapter {
26
+ if (!db) {
27
+ throw new AdapterConfigError(
28
+ 'createAdapter requires a Drizzle db instance. Did you forget to pass it?\n' +
29
+ 'Example: createAdapter(db)',
30
+ );
31
+ }
32
+ return {
33
+ user: {
34
+ async findByIdentifier(identifier) {
35
+ const rows = await db
36
+ .select({
37
+ id: users.id,
38
+ identifier: users.identifier,
39
+ passwordHash: users.passwordHash,
40
+ roleName: roles.name,
41
+ })
42
+ .from(users)
43
+ .leftJoin(userRoles, eq(users.id, userRoles.userId))
44
+ .leftJoin(roles, eq(userRoles.roleId, roles.id))
45
+ .where(eq(users.identifier, identifier));
46
+
47
+ if (rows.length === 0) return null;
48
+ return {
49
+ id: rows[0]!.id,
50
+ identifier: rows[0]!.identifier,
51
+ passwordHash: rows[0]!.passwordHash,
52
+ roles: rows.flatMap((row) => (row.roleName ? [row.roleName] : [])),
53
+ };
54
+ },
55
+
56
+ async findById(id) {
57
+ const rows = await db
58
+ .select({
59
+ id: users.id,
60
+ identifier: users.identifier,
61
+ passwordHash: users.passwordHash,
62
+ roleName: roles.name,
63
+ })
64
+ .from(users)
65
+ .leftJoin(userRoles, eq(users.id, userRoles.userId))
66
+ .leftJoin(roles, eq(userRoles.roleId, roles.id))
67
+ .where(eq(users.id, id));
68
+
69
+ if (rows.length === 0) return null;
70
+ return {
71
+ id: rows[0]!.id,
72
+ identifier: rows[0]!.identifier,
73
+ passwordHash: rows[0]!.passwordHash,
74
+ roles: rows.flatMap((row) => (row.roleName ? [row.roleName] : [])),
75
+ };
76
+ },
77
+
78
+ async create({ identifier, passwordHash, roles: roleNames }) {
79
+ const [user] = await db
80
+ .insert(users)
81
+ .values({ identifier, passwordHash })
82
+ .returning({ id: users.id });
83
+
84
+ if (roleNames.length > 0) {
85
+ await Promise.all(
86
+ roleNames.map(async (name) => {
87
+ const existing = await db.select().from(roles).where(eq(roles.name, name));
88
+ const role = existing[0] ?? (await db.insert(roles).values({ name }).returning())[0];
89
+ await db.insert(userRoles).values({ userId: user!.id, roleId: role!.id });
90
+ }),
91
+ );
92
+ }
93
+ return { id: user!.id };
94
+ },
95
+
96
+ async updateRoles(userId, roleNames) {
97
+ await db.delete(userRoles).where(eq(userRoles.userId, userId));
98
+ await Promise.all(
99
+ roleNames.map(async (name) => {
100
+ const existing = await db.select().from(roles).where(eq(roles.name, name));
101
+ const role = existing[0] ?? (await db.insert(roles).values({ name }).returning())[0];
102
+ await db.insert(userRoles).values({ userId, roleId: role!.id });
103
+ }),
104
+ );
105
+ },
106
+ },
107
+
108
+ session: {
109
+ async create({ userId, expiresAt }) {
110
+ const [session] = await db
111
+ .insert(sessions)
112
+ .values({ userId, expiresAt })
113
+ .returning({ id: sessions.id });
114
+ return { id: session!.id };
115
+ },
116
+
117
+ async findById(sessionId) {
118
+ const rows = await db
119
+ .select({
120
+ sessionId: sessions.id,
121
+ sessionUserId: sessions.userId,
122
+ sessionExpiresAt: sessions.expiresAt,
123
+ sessionCreatedAt: sessions.createdAt,
124
+ userId: users.id,
125
+ identifier: users.identifier,
126
+ passwordHash: users.passwordHash,
127
+ roleName: roles.name,
128
+ })
129
+ .from(sessions)
130
+ .innerJoin(users, eq(sessions.userId, users.id))
131
+ .leftJoin(userRoles, eq(users.id, userRoles.userId))
132
+ .leftJoin(roles, eq(userRoles.roleId, roles.id))
133
+ .where(eq(sessions.id, sessionId));
134
+
135
+ if (rows.length === 0) return null;
136
+ const first = rows[0]!;
137
+ return {
138
+ id: first.sessionId,
139
+ userId: first.sessionUserId,
140
+ expiresAt: first.sessionExpiresAt,
141
+ createdAt: first.sessionCreatedAt,
142
+ user: {
143
+ id: first.userId,
144
+ identifier: first.identifier,
145
+ passwordHash: first.passwordHash,
146
+ roles: rows.flatMap((row) => (row.roleName ? [row.roleName] : [])),
147
+ },
148
+ };
149
+ },
150
+
151
+ async delete(sessionId) {
152
+ await db.delete(sessions).where(eq(sessions.id, sessionId));
153
+ },
154
+
155
+ async deleteAllForUser(userId) {
156
+ await db.delete(sessions).where(eq(sessions.userId, userId));
157
+ },
158
+ },
159
+ };
160
+ }
@@ -0,0 +1,27 @@
1
+ // sentri — auth config template (Drizzle)
2
+ // Generated by: npx sentri generate drizzle
3
+ //
4
+ // Update validRoles to match the roles your application uses.
5
+ // Add JWT_SECRET to your .env file.
6
+ // If you already have a db instance defined elsewhere, import it instead.
7
+
8
+ import { createAuth } from 'sentri';
9
+ import { db } from '../db.js'; // adjust path to your db instance
10
+ import { createAdapter } from './adapter.js';
11
+
12
+ export const auth = createAuth({
13
+ secret: process.env.JWT_SECRET!,
14
+ validRoles: ['user', 'admin'] as const,
15
+ adapter: createAdapter(db),
16
+ // accessExpiresIn: '15m',
17
+ // refreshExpiresIn: '7d',
18
+ // algorithm: 'HS256',
19
+ // saltRounds: 12,
20
+ cookie: {
21
+ secure: process.env.NODE_ENV === 'production',
22
+ // name: 'refresh_token',
23
+ // httpOnly: true,
24
+ // sameSite: 'strict',
25
+ // path: '/',
26
+ },
27
+ });
@@ -0,0 +1,47 @@
1
+ // sentri — Drizzle schema template (PostgreSQL / node-postgres)
2
+ // Generated by: npx sentri generate drizzle
3
+ //
4
+ // Field mapping: library uses `identifier` internally, but your DB column
5
+ // can be named anything — adjust the first argument of text() on the
6
+ // identifier column to match your existing schema, or leave it as-is.
7
+ //
8
+ // text('email') — login via email
9
+ // text('username') — login via username
10
+ // text('phone') — login via phone number
11
+
12
+ import { pgTable, primaryKey, text, timestamp } from 'drizzle-orm/pg-core';
13
+
14
+ export const users = pgTable('users', {
15
+ id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
16
+ identifier: text('email').notNull().unique(),
17
+ passwordHash: text('password_hash').notNull(),
18
+ createdAt: timestamp('created_at').notNull().defaultNow(),
19
+ updatedAt: timestamp('updated_at').notNull().$onUpdateFn(() => new Date()),
20
+ });
21
+
22
+ export const roles = pgTable('roles', {
23
+ id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
24
+ name: text('name').notNull().unique(),
25
+ });
26
+
27
+ export const userRoles = pgTable(
28
+ 'user_roles',
29
+ {
30
+ userId: text('user_id')
31
+ .notNull()
32
+ .references(() => users.id, { onDelete: 'cascade' }),
33
+ roleId: text('role_id')
34
+ .notNull()
35
+ .references(() => roles.id, { onDelete: 'cascade' }),
36
+ },
37
+ (table) => [primaryKey({ columns: [table.userId, table.roleId] })],
38
+ );
39
+
40
+ export const sessions = pgTable('sessions', {
41
+ id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
42
+ userId: text('user_id')
43
+ .notNull()
44
+ .references(() => users.id, { onDelete: 'cascade' }),
45
+ expiresAt: timestamp('expires_at').notNull(),
46
+ createdAt: timestamp('created_at').notNull().defaultNow(),
47
+ });
@@ -0,0 +1,128 @@
1
+ // sentri — Prisma adapter template
2
+ // Generated by: npx sentri generate prisma
3
+ //
4
+ // Adjust the Prisma model/field names below if your schema differs from the
5
+ // template produced by `npx sentri init`.
6
+
7
+ import type { PrismaClient } from '@prisma/client';
8
+ import type { AuthAdapter } from 'sentri';
9
+
10
+ class AdapterConfigError extends Error {
11
+ constructor(message: string) {
12
+ super(message);
13
+ this.name = 'AdapterConfigError';
14
+ }
15
+ }
16
+
17
+ export function createAdapter(prisma: PrismaClient): AuthAdapter {
18
+ if (!prisma) {
19
+ throw new AdapterConfigError(
20
+ 'createAdapter requires a PrismaClient instance. Did you forget to pass it?\n' +
21
+ 'Example: createAdapter(prisma)',
22
+ );
23
+ }
24
+ return {
25
+ user: {
26
+ async findByIdentifier(identifier) {
27
+ const user = await prisma.user.findUnique({
28
+ where: { identifier },
29
+ include: { userRoles: { include: { role: true } } },
30
+ });
31
+ if (!user) return null;
32
+ return {
33
+ id: user.id,
34
+ identifier: user.identifier,
35
+ passwordHash: user.passwordHash,
36
+ roles: user.userRoles.map((userRole) => userRole.role.name),
37
+ };
38
+ },
39
+
40
+ async findById(id) {
41
+ const user = await prisma.user.findUnique({
42
+ where: { id },
43
+ include: { userRoles: { include: { role: true } } },
44
+ });
45
+ if (!user) return null;
46
+ return {
47
+ id: user.id,
48
+ identifier: user.identifier,
49
+ passwordHash: user.passwordHash,
50
+ roles: user.userRoles.map((userRole) => userRole.role.name),
51
+ };
52
+ },
53
+
54
+ async create({ identifier, passwordHash, roles }) {
55
+ const user = await prisma.user.create({
56
+ data: {
57
+ identifier,
58
+ passwordHash,
59
+ userRoles: {
60
+ create: await Promise.all(
61
+ roles.map(async (name) => {
62
+ const role = await prisma.role.upsert({
63
+ where: { name },
64
+ update: {},
65
+ create: { name },
66
+ });
67
+ return { roleId: role.id };
68
+ }),
69
+ ),
70
+ },
71
+ },
72
+ });
73
+ return { id: user.id };
74
+ },
75
+
76
+ async updateRoles(userId, roles) {
77
+ await prisma.userRole.deleteMany({ where: { userId } });
78
+ await Promise.all(
79
+ roles.map(async (name) => {
80
+ const role = await prisma.role.upsert({
81
+ where: { name },
82
+ update: {},
83
+ create: { name },
84
+ });
85
+ await prisma.userRole.create({ data: { userId, roleId: role.id } });
86
+ }),
87
+ );
88
+ },
89
+ },
90
+
91
+ session: {
92
+ async create({ userId, expiresAt }) {
93
+ const session = await prisma.session.create({ data: { userId, expiresAt } });
94
+ return { id: session.id };
95
+ },
96
+
97
+ async findById(sessionId) {
98
+ const session = await prisma.session.findUnique({
99
+ where: { id: sessionId },
100
+ include: {
101
+ user: { include: { userRoles: { include: { role: true } } } },
102
+ },
103
+ });
104
+ if (!session) return null;
105
+ return {
106
+ id: session.id,
107
+ userId: session.userId,
108
+ expiresAt: session.expiresAt,
109
+ createdAt: session.createdAt,
110
+ user: {
111
+ id: session.user.id,
112
+ identifier: session.user.identifier,
113
+ passwordHash: session.user.passwordHash,
114
+ roles: session.user.userRoles.map((userRole) => userRole.role.name),
115
+ },
116
+ };
117
+ },
118
+
119
+ async delete(sessionId) {
120
+ await prisma.session.delete({ where: { id: sessionId } }).catch(() => {});
121
+ },
122
+
123
+ async deleteAllForUser(userId) {
124
+ await prisma.session.deleteMany({ where: { userId } });
125
+ },
126
+ },
127
+ };
128
+ }
@@ -0,0 +1,30 @@
1
+ // sentri — auth config template (Prisma)
2
+ // Generated by: npx sentri generate prisma
3
+ //
4
+ // Update validRoles to match the roles your application uses.
5
+ // Add JWT_SECRET to your .env file.
6
+ // If you already have a PrismaClient instance elsewhere, import it instead of
7
+ // creating a new one here.
8
+
9
+ import { PrismaClient } from '@prisma/client';
10
+ import { createAuth } from 'sentri';
11
+ import { createAdapter } from './adapter.js';
12
+
13
+ const prisma = new PrismaClient();
14
+
15
+ export const auth = createAuth({
16
+ secret: process.env.JWT_SECRET!,
17
+ validRoles: ['user', 'admin'] as const,
18
+ adapter: createAdapter(prisma),
19
+ // accessExpiresIn: '15m',
20
+ // refreshExpiresIn: '7d',
21
+ // algorithm: 'HS256',
22
+ // saltRounds: 12,
23
+ cookie: {
24
+ secure: process.env.NODE_ENV === 'production',
25
+ // name: 'refresh_token',
26
+ // httpOnly: true,
27
+ // sameSite: 'strict',
28
+ // path: '/',
29
+ },
30
+ });