@xcelsior/auth-adapter-knex 1.0.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/.turbo/turbo-build.log +22 -0
- package/biome.json +3 -0
- package/dist/index.d.mts +90 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +336 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +307 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +27 -0
- package/src/index.ts +2 -0
- package/src/knex.ts +351 -0
- package/src/migration.ts +78 -0
- package/tsconfig.json +8 -0
- package/tsup.config.ts +10 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
> @xcelsior/auth-adapter-knex@1.0.0 build /home/circleci/repo/packages/services/auth-adapter-knex
|
|
3
|
+
> tsup && tsc --noEmit
|
|
4
|
+
|
|
5
|
+
[34mCLI[39m Building entry: src/index.ts
|
|
6
|
+
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
|
+
[34mCLI[39m tsup v8.5.1
|
|
8
|
+
[34mCLI[39m Using tsup config: /home/circleci/repo/packages/services/auth-adapter-knex/tsup.config.ts
|
|
9
|
+
[34mCLI[39m Target: es2020
|
|
10
|
+
[34mCLI[39m Cleaning output folder
|
|
11
|
+
[34mCJS[39m Build start
|
|
12
|
+
[34mESM[39m Build start
|
|
13
|
+
[32mCJS[39m [1mdist/index.js [22m[32m11.45 KB[39m
|
|
14
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m22.55 KB[39m
|
|
15
|
+
[32mCJS[39m ⚡️ Build success in 159ms
|
|
16
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m10.31 KB[39m
|
|
17
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m22.33 KB[39m
|
|
18
|
+
[32mESM[39m ⚡️ Build success in 161ms
|
|
19
|
+
[34mDTS[39m Build start
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 20081ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m3.13 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m3.13 KB[39m
|
package/biome.json
ADDED
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Knex } from 'knex';
|
|
2
|
+
import { IStorageProvider, User, UserFilter, FindUsersOptions, FindUsersResult, Session } from '@xcelsior/auth';
|
|
3
|
+
|
|
4
|
+
interface KnexConfig {
|
|
5
|
+
/** Pre-configured Knex instance */
|
|
6
|
+
knex: Knex;
|
|
7
|
+
/** Table name for users */
|
|
8
|
+
tableName: string;
|
|
9
|
+
/** Table name for sessions */
|
|
10
|
+
sessionsTableName: string;
|
|
11
|
+
/** Optional schema (for PostgreSQL) */
|
|
12
|
+
schema?: string;
|
|
13
|
+
}
|
|
14
|
+
declare class KnexStorageProvider implements IStorageProvider {
|
|
15
|
+
private knex;
|
|
16
|
+
private tableName;
|
|
17
|
+
private sessionsTableName;
|
|
18
|
+
private schema?;
|
|
19
|
+
constructor(config: KnexConfig);
|
|
20
|
+
private get table();
|
|
21
|
+
private get sessionsTable();
|
|
22
|
+
createUser(user: User): Promise<void>;
|
|
23
|
+
getUserById(id: string): Promise<User | null>;
|
|
24
|
+
getUserByEmail(email: string): Promise<User | null>;
|
|
25
|
+
getUserByResetPasswordToken(resetPasswordToken: string): Promise<User | null>;
|
|
26
|
+
getUserByVerifyEmailToken(verifyEmailToken: string): Promise<User | null>;
|
|
27
|
+
updateUser(id: string, updates: Partial<User>): Promise<void>;
|
|
28
|
+
deleteUser(id: string): Promise<void>;
|
|
29
|
+
findUsers(filter: UserFilter, options?: FindUsersOptions): Promise<FindUsersResult>;
|
|
30
|
+
createSession(session: Session): Promise<void>;
|
|
31
|
+
getSessionById(id: string): Promise<Session | null>;
|
|
32
|
+
getSessionsByUserId(userId: string): Promise<Session[]>;
|
|
33
|
+
updateSession(id: string, updates: Partial<Session>): Promise<void>;
|
|
34
|
+
deleteSession(id: string): Promise<void>;
|
|
35
|
+
deleteAllUserSessions(userId: string): Promise<void>;
|
|
36
|
+
deleteExpiredSessions(): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Convert User object to database row format (snake_case)
|
|
39
|
+
*/
|
|
40
|
+
private toDbUser;
|
|
41
|
+
/**
|
|
42
|
+
* Convert database row to User object (camelCase)
|
|
43
|
+
*/
|
|
44
|
+
private fromDbUser;
|
|
45
|
+
/**
|
|
46
|
+
* Convert partial User updates to database format
|
|
47
|
+
*/
|
|
48
|
+
private toDbUpdates;
|
|
49
|
+
/**
|
|
50
|
+
* Convert Session object to database row format (snake_case)
|
|
51
|
+
*/
|
|
52
|
+
private toDbSession;
|
|
53
|
+
/**
|
|
54
|
+
* Convert database row to Session object (camelCase)
|
|
55
|
+
*/
|
|
56
|
+
private fromDbSession;
|
|
57
|
+
/**
|
|
58
|
+
* Convert partial Session updates to database format
|
|
59
|
+
*/
|
|
60
|
+
private toDbSessionUpdates;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* SQL migration helper to create the users table
|
|
65
|
+
* Can be used with Knex migrations
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* // In your migration file
|
|
70
|
+
* import { createUsersTableMigration, createSessionsTableMigration } from '@xcelsior/auth-adapter-knex';
|
|
71
|
+
*
|
|
72
|
+
* export async function up(knex: Knex): Promise<void> {
|
|
73
|
+
* await createUsersTableMigration(knex, 'users');
|
|
74
|
+
* await createSessionsTableMigration(knex, 'sessions');
|
|
75
|
+
* }
|
|
76
|
+
*
|
|
77
|
+
* export async function down(knex: Knex): Promise<void> {
|
|
78
|
+
* await knex.schema.dropTableIfExists('sessions');
|
|
79
|
+
* await knex.schema.dropTableIfExists('users');
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare function createUsersTableMigration(knex: Knex, tableName: string, schema?: string): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* SQL migration helper to create the sessions table
|
|
86
|
+
* Can be used with Knex migrations
|
|
87
|
+
*/
|
|
88
|
+
declare function createSessionsTableMigration(knex: Knex, tableName: string, schema?: string): Promise<void>;
|
|
89
|
+
|
|
90
|
+
export { type KnexConfig, KnexStorageProvider, createSessionsTableMigration, createUsersTableMigration };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Knex } from 'knex';
|
|
2
|
+
import { IStorageProvider, User, UserFilter, FindUsersOptions, FindUsersResult, Session } from '@xcelsior/auth';
|
|
3
|
+
|
|
4
|
+
interface KnexConfig {
|
|
5
|
+
/** Pre-configured Knex instance */
|
|
6
|
+
knex: Knex;
|
|
7
|
+
/** Table name for users */
|
|
8
|
+
tableName: string;
|
|
9
|
+
/** Table name for sessions */
|
|
10
|
+
sessionsTableName: string;
|
|
11
|
+
/** Optional schema (for PostgreSQL) */
|
|
12
|
+
schema?: string;
|
|
13
|
+
}
|
|
14
|
+
declare class KnexStorageProvider implements IStorageProvider {
|
|
15
|
+
private knex;
|
|
16
|
+
private tableName;
|
|
17
|
+
private sessionsTableName;
|
|
18
|
+
private schema?;
|
|
19
|
+
constructor(config: KnexConfig);
|
|
20
|
+
private get table();
|
|
21
|
+
private get sessionsTable();
|
|
22
|
+
createUser(user: User): Promise<void>;
|
|
23
|
+
getUserById(id: string): Promise<User | null>;
|
|
24
|
+
getUserByEmail(email: string): Promise<User | null>;
|
|
25
|
+
getUserByResetPasswordToken(resetPasswordToken: string): Promise<User | null>;
|
|
26
|
+
getUserByVerifyEmailToken(verifyEmailToken: string): Promise<User | null>;
|
|
27
|
+
updateUser(id: string, updates: Partial<User>): Promise<void>;
|
|
28
|
+
deleteUser(id: string): Promise<void>;
|
|
29
|
+
findUsers(filter: UserFilter, options?: FindUsersOptions): Promise<FindUsersResult>;
|
|
30
|
+
createSession(session: Session): Promise<void>;
|
|
31
|
+
getSessionById(id: string): Promise<Session | null>;
|
|
32
|
+
getSessionsByUserId(userId: string): Promise<Session[]>;
|
|
33
|
+
updateSession(id: string, updates: Partial<Session>): Promise<void>;
|
|
34
|
+
deleteSession(id: string): Promise<void>;
|
|
35
|
+
deleteAllUserSessions(userId: string): Promise<void>;
|
|
36
|
+
deleteExpiredSessions(): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Convert User object to database row format (snake_case)
|
|
39
|
+
*/
|
|
40
|
+
private toDbUser;
|
|
41
|
+
/**
|
|
42
|
+
* Convert database row to User object (camelCase)
|
|
43
|
+
*/
|
|
44
|
+
private fromDbUser;
|
|
45
|
+
/**
|
|
46
|
+
* Convert partial User updates to database format
|
|
47
|
+
*/
|
|
48
|
+
private toDbUpdates;
|
|
49
|
+
/**
|
|
50
|
+
* Convert Session object to database row format (snake_case)
|
|
51
|
+
*/
|
|
52
|
+
private toDbSession;
|
|
53
|
+
/**
|
|
54
|
+
* Convert database row to Session object (camelCase)
|
|
55
|
+
*/
|
|
56
|
+
private fromDbSession;
|
|
57
|
+
/**
|
|
58
|
+
* Convert partial Session updates to database format
|
|
59
|
+
*/
|
|
60
|
+
private toDbSessionUpdates;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* SQL migration helper to create the users table
|
|
65
|
+
* Can be used with Knex migrations
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* // In your migration file
|
|
70
|
+
* import { createUsersTableMigration, createSessionsTableMigration } from '@xcelsior/auth-adapter-knex';
|
|
71
|
+
*
|
|
72
|
+
* export async function up(knex: Knex): Promise<void> {
|
|
73
|
+
* await createUsersTableMigration(knex, 'users');
|
|
74
|
+
* await createSessionsTableMigration(knex, 'sessions');
|
|
75
|
+
* }
|
|
76
|
+
*
|
|
77
|
+
* export async function down(knex: Knex): Promise<void> {
|
|
78
|
+
* await knex.schema.dropTableIfExists('sessions');
|
|
79
|
+
* await knex.schema.dropTableIfExists('users');
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare function createUsersTableMigration(knex: Knex, tableName: string, schema?: string): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* SQL migration helper to create the sessions table
|
|
86
|
+
* Can be used with Knex migrations
|
|
87
|
+
*/
|
|
88
|
+
declare function createSessionsTableMigration(knex: Knex, tableName: string, schema?: string): Promise<void>;
|
|
89
|
+
|
|
90
|
+
export { type KnexConfig, KnexStorageProvider, createSessionsTableMigration, createUsersTableMigration };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
KnexStorageProvider: () => KnexStorageProvider,
|
|
24
|
+
createSessionsTableMigration: () => createSessionsTableMigration,
|
|
25
|
+
createUsersTableMigration: () => createUsersTableMigration
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
|
|
29
|
+
// src/knex.ts
|
|
30
|
+
var KnexStorageProvider = class {
|
|
31
|
+
constructor(config) {
|
|
32
|
+
this.knex = config.knex;
|
|
33
|
+
this.tableName = config.tableName;
|
|
34
|
+
this.sessionsTableName = config.sessionsTableName;
|
|
35
|
+
this.schema = config.schema;
|
|
36
|
+
}
|
|
37
|
+
get table() {
|
|
38
|
+
const query = this.knex(this.tableName);
|
|
39
|
+
if (this.schema) {
|
|
40
|
+
return query.withSchema(this.schema);
|
|
41
|
+
}
|
|
42
|
+
return query;
|
|
43
|
+
}
|
|
44
|
+
get sessionsTable() {
|
|
45
|
+
const query = this.knex(this.sessionsTableName);
|
|
46
|
+
if (this.schema) {
|
|
47
|
+
return query.withSchema(this.schema);
|
|
48
|
+
}
|
|
49
|
+
return query;
|
|
50
|
+
}
|
|
51
|
+
// ==================== User Methods ====================
|
|
52
|
+
async createUser(user) {
|
|
53
|
+
await this.table.insert(this.toDbUser(user));
|
|
54
|
+
}
|
|
55
|
+
async getUserById(id) {
|
|
56
|
+
const row = await this.table.where({ id }).first();
|
|
57
|
+
return row ? this.fromDbUser(row) : null;
|
|
58
|
+
}
|
|
59
|
+
async getUserByEmail(email) {
|
|
60
|
+
const row = await this.table.where({ email }).first();
|
|
61
|
+
return row ? this.fromDbUser(row) : null;
|
|
62
|
+
}
|
|
63
|
+
async getUserByResetPasswordToken(resetPasswordToken) {
|
|
64
|
+
const row = await this.table.where({ reset_password_token: resetPasswordToken }).first();
|
|
65
|
+
return row ? this.fromDbUser(row) : null;
|
|
66
|
+
}
|
|
67
|
+
async getUserByVerifyEmailToken(verifyEmailToken) {
|
|
68
|
+
const row = await this.table.where({ verification_token: verifyEmailToken }).first();
|
|
69
|
+
return row ? this.fromDbUser(row) : null;
|
|
70
|
+
}
|
|
71
|
+
async updateUser(id, updates) {
|
|
72
|
+
const dbUpdates = this.toDbUpdates(updates);
|
|
73
|
+
if (Object.keys(dbUpdates).length === 0) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
await this.table.where({ id }).update(dbUpdates);
|
|
77
|
+
}
|
|
78
|
+
async deleteUser(id) {
|
|
79
|
+
await this.deleteAllUserSessions(id);
|
|
80
|
+
await this.table.where({ id }).delete();
|
|
81
|
+
}
|
|
82
|
+
async findUsers(filter, options = {}) {
|
|
83
|
+
const limit = options.limit ?? 50;
|
|
84
|
+
let query = this.table.clone();
|
|
85
|
+
if (filter.email) {
|
|
86
|
+
query = query.where("email", filter.email);
|
|
87
|
+
}
|
|
88
|
+
if (filter.emailContains) {
|
|
89
|
+
query = query.where("email", "like", `%${filter.emailContains}%`);
|
|
90
|
+
}
|
|
91
|
+
if (filter.isEmailVerified !== void 0) {
|
|
92
|
+
query = query.where("is_email_verified", filter.isEmailVerified);
|
|
93
|
+
}
|
|
94
|
+
if (filter.roles && filter.roles.length > 0) {
|
|
95
|
+
for (const role of filter.roles) {
|
|
96
|
+
query = query.whereRaw(
|
|
97
|
+
this.knex.client.config.client === "sqlite3" ? `roles LIKE ?` : `roles @> ?`,
|
|
98
|
+
this.knex.client.config.client === "sqlite3" ? [`%"${role}"%`] : [JSON.stringify([role])]
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (filter.hasAnyRole && filter.hasAnyRole.length > 0) {
|
|
103
|
+
query = query.where((builder) => {
|
|
104
|
+
for (const role of filter.hasAnyRole) {
|
|
105
|
+
builder.orWhereRaw(
|
|
106
|
+
this.knex.client.config.client === "sqlite3" ? `roles LIKE ?` : `roles @> ?`,
|
|
107
|
+
this.knex.client.config.client === "sqlite3" ? [`%"${role}"%`] : [JSON.stringify([role])]
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (options.cursor) {
|
|
113
|
+
const cursorData = JSON.parse(Buffer.from(options.cursor, "base64").toString());
|
|
114
|
+
query = query.where("id", ">", cursorData.lastId);
|
|
115
|
+
}
|
|
116
|
+
query = query.orderBy("id", "asc").limit(limit + 1);
|
|
117
|
+
const rows = await query;
|
|
118
|
+
const hasMore = rows.length > limit;
|
|
119
|
+
const resultRows = hasMore ? rows.slice(0, limit) : rows;
|
|
120
|
+
const users = resultRows.map((row) => this.fromDbUser(row));
|
|
121
|
+
let nextCursor;
|
|
122
|
+
if (hasMore && resultRows.length > 0) {
|
|
123
|
+
const lastUser = resultRows[resultRows.length - 1];
|
|
124
|
+
nextCursor = Buffer.from(JSON.stringify({ lastId: lastUser.id })).toString("base64");
|
|
125
|
+
}
|
|
126
|
+
return { users, nextCursor };
|
|
127
|
+
}
|
|
128
|
+
// ==================== Session Methods ====================
|
|
129
|
+
async createSession(session) {
|
|
130
|
+
await this.sessionsTable.insert(this.toDbSession(session));
|
|
131
|
+
}
|
|
132
|
+
async getSessionById(id) {
|
|
133
|
+
const row = await this.sessionsTable.where({ id }).first();
|
|
134
|
+
return row ? this.fromDbSession(row) : null;
|
|
135
|
+
}
|
|
136
|
+
async getSessionsByUserId(userId) {
|
|
137
|
+
const rows = await this.sessionsTable.where({ user_id: userId });
|
|
138
|
+
return rows.map((row) => this.fromDbSession(row));
|
|
139
|
+
}
|
|
140
|
+
async updateSession(id, updates) {
|
|
141
|
+
const dbUpdates = this.toDbSessionUpdates(updates);
|
|
142
|
+
if (Object.keys(dbUpdates).length === 0) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
await this.sessionsTable.where({ id }).update(dbUpdates);
|
|
146
|
+
}
|
|
147
|
+
async deleteSession(id) {
|
|
148
|
+
await this.sessionsTable.where({ id }).delete();
|
|
149
|
+
}
|
|
150
|
+
async deleteAllUserSessions(userId) {
|
|
151
|
+
await this.sessionsTable.where({ user_id: userId }).delete();
|
|
152
|
+
}
|
|
153
|
+
async deleteExpiredSessions() {
|
|
154
|
+
const now = Date.now();
|
|
155
|
+
await this.sessionsTable.where("expires_at", "<", now).delete();
|
|
156
|
+
}
|
|
157
|
+
// ==================== User Mapping Helpers ====================
|
|
158
|
+
/**
|
|
159
|
+
* Convert User object to database row format (snake_case)
|
|
160
|
+
*/
|
|
161
|
+
toDbUser(user) {
|
|
162
|
+
return {
|
|
163
|
+
id: user.id,
|
|
164
|
+
email: user.email,
|
|
165
|
+
first_name: user.firstName ?? null,
|
|
166
|
+
last_name: user.lastName ?? null,
|
|
167
|
+
password_hash: user.passwordHash,
|
|
168
|
+
roles: JSON.stringify(user.roles),
|
|
169
|
+
is_email_verified: user.isEmailVerified,
|
|
170
|
+
verification_token: user.verificationToken ?? null,
|
|
171
|
+
reset_password_token: user.resetPasswordToken ?? null,
|
|
172
|
+
reset_password_expires: user.resetPasswordExpires ?? null,
|
|
173
|
+
created_at: user.createdAt,
|
|
174
|
+
updated_at: user.updatedAt
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Convert database row to User object (camelCase)
|
|
179
|
+
*/
|
|
180
|
+
fromDbUser(row) {
|
|
181
|
+
return {
|
|
182
|
+
id: row.id,
|
|
183
|
+
email: row.email,
|
|
184
|
+
firstName: row.first_name,
|
|
185
|
+
lastName: row.last_name,
|
|
186
|
+
passwordHash: row.password_hash,
|
|
187
|
+
roles: typeof row.roles === "string" ? JSON.parse(row.roles) : row.roles,
|
|
188
|
+
isEmailVerified: Boolean(row.is_email_verified),
|
|
189
|
+
verificationToken: row.verification_token,
|
|
190
|
+
resetPasswordToken: row.reset_password_token,
|
|
191
|
+
resetPasswordExpires: row.reset_password_expires,
|
|
192
|
+
createdAt: row.created_at,
|
|
193
|
+
updatedAt: row.updated_at
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Convert partial User updates to database format
|
|
198
|
+
*/
|
|
199
|
+
toDbUpdates(updates) {
|
|
200
|
+
const dbUpdates = {};
|
|
201
|
+
if (updates.email !== void 0) {
|
|
202
|
+
dbUpdates.email = updates.email;
|
|
203
|
+
}
|
|
204
|
+
if ("firstName" in updates) {
|
|
205
|
+
dbUpdates.first_name = updates.firstName ?? null;
|
|
206
|
+
}
|
|
207
|
+
if ("lastName" in updates) {
|
|
208
|
+
dbUpdates.last_name = updates.lastName ?? null;
|
|
209
|
+
}
|
|
210
|
+
if (updates.passwordHash !== void 0) {
|
|
211
|
+
dbUpdates.password_hash = updates.passwordHash;
|
|
212
|
+
}
|
|
213
|
+
if (updates.roles !== void 0) {
|
|
214
|
+
dbUpdates.roles = JSON.stringify(updates.roles);
|
|
215
|
+
}
|
|
216
|
+
if (updates.isEmailVerified !== void 0) {
|
|
217
|
+
dbUpdates.is_email_verified = updates.isEmailVerified;
|
|
218
|
+
}
|
|
219
|
+
if ("verificationToken" in updates) {
|
|
220
|
+
dbUpdates.verification_token = updates.verificationToken ?? null;
|
|
221
|
+
}
|
|
222
|
+
if ("resetPasswordToken" in updates) {
|
|
223
|
+
dbUpdates.reset_password_token = updates.resetPasswordToken ?? null;
|
|
224
|
+
}
|
|
225
|
+
if ("resetPasswordExpires" in updates) {
|
|
226
|
+
dbUpdates.reset_password_expires = updates.resetPasswordExpires ?? null;
|
|
227
|
+
}
|
|
228
|
+
if (updates.updatedAt !== void 0) {
|
|
229
|
+
dbUpdates.updated_at = updates.updatedAt;
|
|
230
|
+
}
|
|
231
|
+
return dbUpdates;
|
|
232
|
+
}
|
|
233
|
+
// ==================== Session Mapping Helpers ====================
|
|
234
|
+
/**
|
|
235
|
+
* Convert Session object to database row format (snake_case)
|
|
236
|
+
*/
|
|
237
|
+
toDbSession(session) {
|
|
238
|
+
return {
|
|
239
|
+
id: session.id,
|
|
240
|
+
user_id: session.userId,
|
|
241
|
+
refresh_token_hash: session.refreshTokenHash,
|
|
242
|
+
user_agent: session.userAgent ?? null,
|
|
243
|
+
ip_address: session.ipAddress ?? null,
|
|
244
|
+
device_name: session.deviceName ?? null,
|
|
245
|
+
created_at: session.createdAt,
|
|
246
|
+
last_used_at: session.lastUsedAt,
|
|
247
|
+
expires_at: session.expiresAt
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Convert database row to Session object (camelCase)
|
|
252
|
+
*/
|
|
253
|
+
fromDbSession(row) {
|
|
254
|
+
return {
|
|
255
|
+
id: row.id,
|
|
256
|
+
userId: row.user_id,
|
|
257
|
+
refreshTokenHash: row.refresh_token_hash,
|
|
258
|
+
userAgent: row.user_agent,
|
|
259
|
+
ipAddress: row.ip_address,
|
|
260
|
+
deviceName: row.device_name,
|
|
261
|
+
createdAt: row.created_at,
|
|
262
|
+
lastUsedAt: row.last_used_at,
|
|
263
|
+
expiresAt: row.expires_at
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Convert partial Session updates to database format
|
|
268
|
+
*/
|
|
269
|
+
toDbSessionUpdates(updates) {
|
|
270
|
+
const dbUpdates = {};
|
|
271
|
+
if (updates.refreshTokenHash !== void 0) {
|
|
272
|
+
dbUpdates.refresh_token_hash = updates.refreshTokenHash;
|
|
273
|
+
}
|
|
274
|
+
if ("userAgent" in updates) {
|
|
275
|
+
dbUpdates.user_agent = updates.userAgent ?? null;
|
|
276
|
+
}
|
|
277
|
+
if ("ipAddress" in updates) {
|
|
278
|
+
dbUpdates.ip_address = updates.ipAddress ?? null;
|
|
279
|
+
}
|
|
280
|
+
if ("deviceName" in updates) {
|
|
281
|
+
dbUpdates.device_name = updates.deviceName ?? null;
|
|
282
|
+
}
|
|
283
|
+
if (updates.lastUsedAt !== void 0) {
|
|
284
|
+
dbUpdates.last_used_at = updates.lastUsedAt;
|
|
285
|
+
}
|
|
286
|
+
if (updates.expiresAt !== void 0) {
|
|
287
|
+
dbUpdates.expires_at = updates.expiresAt;
|
|
288
|
+
}
|
|
289
|
+
return dbUpdates;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// src/migration.ts
|
|
294
|
+
async function createUsersTableMigration(knex, tableName, schema) {
|
|
295
|
+
const schemaBuilder = schema ? knex.schema.withSchema(schema) : knex.schema;
|
|
296
|
+
await schemaBuilder.createTable(tableName, (table) => {
|
|
297
|
+
table.string("id").primary();
|
|
298
|
+
table.string("email").notNullable().unique();
|
|
299
|
+
table.string("first_name").nullable();
|
|
300
|
+
table.string("last_name").nullable();
|
|
301
|
+
table.string("password_hash").notNullable();
|
|
302
|
+
table.jsonb("roles").notNullable();
|
|
303
|
+
table.boolean("is_email_verified").notNullable().defaultTo(false);
|
|
304
|
+
table.string("verification_token").nullable();
|
|
305
|
+
table.string("reset_password_token").nullable();
|
|
306
|
+
table.bigInteger("reset_password_expires").nullable();
|
|
307
|
+
table.bigInteger("created_at").notNullable();
|
|
308
|
+
table.bigInteger("updated_at").notNullable();
|
|
309
|
+
table.index("email");
|
|
310
|
+
table.index("verification_token");
|
|
311
|
+
table.index("reset_password_token");
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
async function createSessionsTableMigration(knex, tableName, schema) {
|
|
315
|
+
const schemaBuilder = schema ? knex.schema.withSchema(schema) : knex.schema;
|
|
316
|
+
await schemaBuilder.createTable(tableName, (table) => {
|
|
317
|
+
table.string("id").primary();
|
|
318
|
+
table.string("user_id").notNullable();
|
|
319
|
+
table.string("refresh_token_hash").notNullable();
|
|
320
|
+
table.string("user_agent").nullable();
|
|
321
|
+
table.string("ip_address").nullable();
|
|
322
|
+
table.string("device_name").nullable();
|
|
323
|
+
table.bigInteger("created_at").notNullable();
|
|
324
|
+
table.bigInteger("last_used_at").notNullable();
|
|
325
|
+
table.bigInteger("expires_at").notNullable();
|
|
326
|
+
table.index("user_id");
|
|
327
|
+
table.index("expires_at");
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
331
|
+
0 && (module.exports = {
|
|
332
|
+
KnexStorageProvider,
|
|
333
|
+
createSessionsTableMigration,
|
|
334
|
+
createUsersTableMigration
|
|
335
|
+
});
|
|
336
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/knex.ts","../src/migration.ts"],"sourcesContent":["export { KnexStorageProvider, type KnexConfig } from './knex';\nexport { createUsersTableMigration, createSessionsTableMigration } from './migration';\n","import type { Knex } from 'knex';\nimport type {\n IStorageProvider,\n User,\n Session,\n UserFilter,\n FindUsersOptions,\n FindUsersResult,\n} from '@xcelsior/auth';\n\nexport interface KnexConfig {\n /** Pre-configured Knex instance */\n knex: Knex;\n /** Table name for users */\n tableName: string;\n /** Table name for sessions */\n sessionsTableName: string;\n /** Optional schema (for PostgreSQL) */\n schema?: string;\n}\n\nexport class KnexStorageProvider implements IStorageProvider {\n private knex: Knex;\n private tableName: string;\n private sessionsTableName: string;\n private schema?: string;\n\n constructor(config: KnexConfig) {\n this.knex = config.knex;\n this.tableName = config.tableName;\n this.sessionsTableName = config.sessionsTableName;\n this.schema = config.schema;\n }\n\n private get table() {\n const query = this.knex(this.tableName);\n if (this.schema) {\n return query.withSchema(this.schema);\n }\n return query;\n }\n\n private get sessionsTable() {\n const query = this.knex(this.sessionsTableName);\n if (this.schema) {\n return query.withSchema(this.schema);\n }\n return query;\n }\n\n // ==================== User Methods ====================\n\n async createUser(user: User): Promise<void> {\n await this.table.insert(this.toDbUser(user));\n }\n\n async getUserById(id: string): Promise<User | null> {\n const row = await this.table.where({ id }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async getUserByEmail(email: string): Promise<User | null> {\n const row = await this.table.where({ email }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async getUserByResetPasswordToken(resetPasswordToken: string): Promise<User | null> {\n const row = await this.table.where({ reset_password_token: resetPasswordToken }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async getUserByVerifyEmailToken(verifyEmailToken: string): Promise<User | null> {\n const row = await this.table.where({ verification_token: verifyEmailToken }).first();\n return row ? this.fromDbUser(row) : null;\n }\n\n async updateUser(id: string, updates: Partial<User>): Promise<void> {\n const dbUpdates = this.toDbUpdates(updates);\n\n if (Object.keys(dbUpdates).length === 0) {\n return;\n }\n\n await this.table.where({ id }).update(dbUpdates);\n }\n\n async deleteUser(id: string): Promise<void> {\n // Delete all user sessions first\n await this.deleteAllUserSessions(id);\n // Then delete the user\n await this.table.where({ id }).delete();\n }\n\n async findUsers(filter: UserFilter, options: FindUsersOptions = {}): Promise<FindUsersResult> {\n const limit = options.limit ?? 50;\n\n let query = this.table.clone();\n\n // Email exact match\n if (filter.email) {\n query = query.where('email', filter.email);\n }\n\n // Email contains (partial match)\n if (filter.emailContains) {\n query = query.where('email', 'like', `%${filter.emailContains}%`);\n }\n\n // Email verification status\n if (filter.isEmailVerified !== undefined) {\n query = query.where('is_email_verified', filter.isEmailVerified);\n }\n\n // Roles filtering - user must have ALL specified roles\n if (filter.roles && filter.roles.length > 0) {\n for (const role of filter.roles) {\n // Use JSON contains - works for PostgreSQL (jsonb) and MySQL (json)\n // For SQLite, we fall back to LIKE on the JSON string\n query = query.whereRaw(\n this.knex.client.config.client === 'sqlite3' ? `roles LIKE ?` : `roles @> ?`,\n this.knex.client.config.client === 'sqlite3'\n ? [`%\"${role}\"%`]\n : [JSON.stringify([role])]\n );\n }\n }\n\n // HasAnyRole - user must have at least ONE of specified roles\n if (filter.hasAnyRole && filter.hasAnyRole.length > 0) {\n query = query.where((builder: Knex.QueryBuilder) => {\n for (const role of filter.hasAnyRole!) {\n builder.orWhereRaw(\n this.knex.client.config.client === 'sqlite3'\n ? `roles LIKE ?`\n : `roles @> ?`,\n this.knex.client.config.client === 'sqlite3'\n ? [`%\"${role}\"%`]\n : [JSON.stringify([role])]\n );\n }\n });\n }\n\n // Apply pagination\n if (options.cursor) {\n const cursorData = JSON.parse(Buffer.from(options.cursor, 'base64').toString());\n query = query.where('id', '>', cursorData.lastId);\n }\n\n // Order by id for consistent pagination\n query = query.orderBy('id', 'asc').limit(limit + 1);\n\n const rows = await query;\n const hasMore = rows.length > limit;\n const resultRows = hasMore ? rows.slice(0, limit) : rows;\n const users = resultRows.map((row: Record<string, unknown>) => this.fromDbUser(row));\n\n let nextCursor: string | undefined;\n if (hasMore && resultRows.length > 0) {\n const lastUser = resultRows[resultRows.length - 1];\n nextCursor = Buffer.from(JSON.stringify({ lastId: lastUser.id })).toString('base64');\n }\n\n return { users, nextCursor };\n }\n\n // ==================== Session Methods ====================\n\n async createSession(session: Session): Promise<void> {\n await this.sessionsTable.insert(this.toDbSession(session));\n }\n\n async getSessionById(id: string): Promise<Session | null> {\n const row = await this.sessionsTable.where({ id }).first();\n return row ? this.fromDbSession(row) : null;\n }\n\n async getSessionsByUserId(userId: string): Promise<Session[]> {\n const rows = await this.sessionsTable.where({ user_id: userId });\n return rows.map((row: Record<string, unknown>) => this.fromDbSession(row));\n }\n\n async updateSession(id: string, updates: Partial<Session>): Promise<void> {\n const dbUpdates = this.toDbSessionUpdates(updates);\n\n if (Object.keys(dbUpdates).length === 0) {\n return;\n }\n\n await this.sessionsTable.where({ id }).update(dbUpdates);\n }\n\n async deleteSession(id: string): Promise<void> {\n await this.sessionsTable.where({ id }).delete();\n }\n\n async deleteAllUserSessions(userId: string): Promise<void> {\n await this.sessionsTable.where({ user_id: userId }).delete();\n }\n\n async deleteExpiredSessions(): Promise<void> {\n const now = Date.now();\n await this.sessionsTable.where('expires_at', '<', now).delete();\n }\n\n // ==================== User Mapping Helpers ====================\n\n /**\n * Convert User object to database row format (snake_case)\n */\n private toDbUser(user: User): Record<string, unknown> {\n return {\n id: user.id,\n email: user.email,\n first_name: user.firstName ?? null,\n last_name: user.lastName ?? null,\n password_hash: user.passwordHash,\n roles: JSON.stringify(user.roles),\n is_email_verified: user.isEmailVerified,\n verification_token: user.verificationToken ?? null,\n reset_password_token: user.resetPasswordToken ?? null,\n reset_password_expires: user.resetPasswordExpires ?? null,\n created_at: user.createdAt,\n updated_at: user.updatedAt,\n };\n }\n\n /**\n * Convert database row to User object (camelCase)\n */\n private fromDbUser(row: Record<string, unknown>): User {\n return {\n id: row.id as string,\n email: row.email as string,\n firstName: row.first_name as string | undefined,\n lastName: row.last_name as string | undefined,\n passwordHash: row.password_hash as string,\n roles: typeof row.roles === 'string' ? JSON.parse(row.roles) : row.roles,\n isEmailVerified: Boolean(row.is_email_verified),\n verificationToken: row.verification_token as string | undefined,\n resetPasswordToken: row.reset_password_token as string | undefined,\n resetPasswordExpires: row.reset_password_expires as number | undefined,\n createdAt: row.created_at as number,\n updatedAt: row.updated_at as number,\n };\n }\n\n /**\n * Convert partial User updates to database format\n */\n private toDbUpdates(updates: Partial<User>): Record<string, unknown> {\n const dbUpdates: Record<string, unknown> = {};\n\n if (updates.email !== undefined) {\n dbUpdates.email = updates.email;\n }\n if ('firstName' in updates) {\n dbUpdates.first_name = updates.firstName ?? null;\n }\n if ('lastName' in updates) {\n dbUpdates.last_name = updates.lastName ?? null;\n }\n if (updates.passwordHash !== undefined) {\n dbUpdates.password_hash = updates.passwordHash;\n }\n if (updates.roles !== undefined) {\n dbUpdates.roles = JSON.stringify(updates.roles);\n }\n if (updates.isEmailVerified !== undefined) {\n dbUpdates.is_email_verified = updates.isEmailVerified;\n }\n if ('verificationToken' in updates) {\n dbUpdates.verification_token = updates.verificationToken ?? null;\n }\n if ('resetPasswordToken' in updates) {\n dbUpdates.reset_password_token = updates.resetPasswordToken ?? null;\n }\n if ('resetPasswordExpires' in updates) {\n dbUpdates.reset_password_expires = updates.resetPasswordExpires ?? null;\n }\n if (updates.updatedAt !== undefined) {\n dbUpdates.updated_at = updates.updatedAt;\n }\n\n return dbUpdates;\n }\n\n // ==================== Session Mapping Helpers ====================\n\n /**\n * Convert Session object to database row format (snake_case)\n */\n private toDbSession(session: Session): Record<string, unknown> {\n return {\n id: session.id,\n user_id: session.userId,\n refresh_token_hash: session.refreshTokenHash,\n user_agent: session.userAgent ?? null,\n ip_address: session.ipAddress ?? null,\n device_name: session.deviceName ?? null,\n created_at: session.createdAt,\n last_used_at: session.lastUsedAt,\n expires_at: session.expiresAt,\n };\n }\n\n /**\n * Convert database row to Session object (camelCase)\n */\n private fromDbSession(row: Record<string, unknown>): Session {\n return {\n id: row.id as string,\n userId: row.user_id as string,\n refreshTokenHash: row.refresh_token_hash as string,\n userAgent: row.user_agent as string | undefined,\n ipAddress: row.ip_address as string | undefined,\n deviceName: row.device_name as string | undefined,\n createdAt: row.created_at as number,\n lastUsedAt: row.last_used_at as number,\n expiresAt: row.expires_at as number,\n };\n }\n\n /**\n * Convert partial Session updates to database format\n */\n private toDbSessionUpdates(updates: Partial<Session>): Record<string, unknown> {\n const dbUpdates: Record<string, unknown> = {};\n\n if (updates.refreshTokenHash !== undefined) {\n dbUpdates.refresh_token_hash = updates.refreshTokenHash;\n }\n if ('userAgent' in updates) {\n dbUpdates.user_agent = updates.userAgent ?? null;\n }\n if ('ipAddress' in updates) {\n dbUpdates.ip_address = updates.ipAddress ?? null;\n }\n if ('deviceName' in updates) {\n dbUpdates.device_name = updates.deviceName ?? null;\n }\n if (updates.lastUsedAt !== undefined) {\n dbUpdates.last_used_at = updates.lastUsedAt;\n }\n if (updates.expiresAt !== undefined) {\n dbUpdates.expires_at = updates.expiresAt;\n }\n\n return dbUpdates;\n }\n}\n","import type { Knex } from 'knex';\n\n/**\n * SQL migration helper to create the users table\n * Can be used with Knex migrations\n *\n * @example\n * ```ts\n * // In your migration file\n * import { createUsersTableMigration, createSessionsTableMigration } from '@xcelsior/auth-adapter-knex';\n *\n * export async function up(knex: Knex): Promise<void> {\n * await createUsersTableMigration(knex, 'users');\n * await createSessionsTableMigration(knex, 'sessions');\n * }\n *\n * export async function down(knex: Knex): Promise<void> {\n * await knex.schema.dropTableIfExists('sessions');\n * await knex.schema.dropTableIfExists('users');\n * }\n * ```\n */\nexport async function createUsersTableMigration(\n knex: Knex,\n tableName: string,\n schema?: string\n): Promise<void> {\n const schemaBuilder = schema ? knex.schema.withSchema(schema) : knex.schema;\n\n await schemaBuilder.createTable(tableName, (table) => {\n table.string('id').primary();\n table.string('email').notNullable().unique();\n table.string('first_name').nullable();\n table.string('last_name').nullable();\n table.string('password_hash').notNullable();\n table.jsonb('roles').notNullable();\n table.boolean('is_email_verified').notNullable().defaultTo(false);\n table.string('verification_token').nullable();\n table.string('reset_password_token').nullable();\n table.bigInteger('reset_password_expires').nullable();\n table.bigInteger('created_at').notNullable();\n table.bigInteger('updated_at').notNullable();\n\n // Indexes for common lookups\n table.index('email');\n table.index('verification_token');\n table.index('reset_password_token');\n });\n}\n\n/**\n * SQL migration helper to create the sessions table\n * Can be used with Knex migrations\n */\nexport async function createSessionsTableMigration(\n knex: Knex,\n tableName: string,\n schema?: string\n): Promise<void> {\n const schemaBuilder = schema ? knex.schema.withSchema(schema) : knex.schema;\n\n await schemaBuilder.createTable(tableName, (table) => {\n table.string('id').primary();\n table.string('user_id').notNullable();\n table.string('refresh_token_hash').notNullable();\n table.string('user_agent').nullable();\n table.string('ip_address').nullable();\n table.string('device_name').nullable();\n table.bigInteger('created_at').notNullable();\n table.bigInteger('last_used_at').notNullable();\n table.bigInteger('expires_at').notNullable();\n\n // Index for querying sessions by user\n table.index('user_id');\n // Index for cleaning up expired sessions\n table.index('expires_at');\n });\n}\n"],"mappings":"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","names":[]}
|