@fortressauth/adapter-sql 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 +21 -0
- package/README.md +135 -0
- package/dist/adapter.d.ts +33 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +224 -0
- package/dist/adapter.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/migrations/001_initial.d.ts +4 -0
- package/dist/migrations/001_initial.d.ts.map +1 -0
- package/dist/migrations/001_initial.js +62 -0
- package/dist/migrations/001_initial.js.map +1 -0
- package/dist/schema.d.ts +41 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +2 -0
- package/dist/schema.js.map +1 -0
- package/package.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Damilola Michael Ige
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# @fortressauth/adapter-sql
|
|
2
|
+
|
|
3
|
+
SQL database adapter for FortressAuth using Kysely query builder.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🗄️ **Multi-Database Support**: PostgreSQL, MySQL, SQLite
|
|
8
|
+
- 🔍 **Type-Safe Queries**: Powered by Kysely
|
|
9
|
+
- 🔄 **Transactions**: Full transaction support
|
|
10
|
+
- 📊 **Migrations**: Built-in migration system
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @fortressauth/adapter-sql @fortressauth/core kysely
|
|
16
|
+
# or
|
|
17
|
+
pnpm add @fortressauth/adapter-sql @fortressauth/core kysely
|
|
18
|
+
# or
|
|
19
|
+
yarn add @fortressauth/adapter-sql @fortressauth/core kysely
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
You'll also need a database driver:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# For PostgreSQL
|
|
26
|
+
npm install pg
|
|
27
|
+
|
|
28
|
+
# For MySQL
|
|
29
|
+
npm install mysql2
|
|
30
|
+
|
|
31
|
+
# For SQLite
|
|
32
|
+
npm install better-sqlite3
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
### SQLite
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import Database from 'better-sqlite3';
|
|
41
|
+
import { Kysely, SqliteDialect } from 'kysely';
|
|
42
|
+
import { SqlAdapter, up } from '@fortressauth/adapter-sql';
|
|
43
|
+
|
|
44
|
+
const sqlite = new Database('./auth.db');
|
|
45
|
+
const db = new Kysely({
|
|
46
|
+
dialect: new SqliteDialect({ database: sqlite }),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Run migrations
|
|
50
|
+
await up(db);
|
|
51
|
+
|
|
52
|
+
// Create adapter
|
|
53
|
+
const adapter = new SqlAdapter(db, { dialect: 'sqlite' });
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### PostgreSQL
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { Kysely, PostgresDialect } from 'kysely';
|
|
60
|
+
import { Pool } from 'pg';
|
|
61
|
+
import { SqlAdapter, up } from '@fortressauth/adapter-sql';
|
|
62
|
+
|
|
63
|
+
const db = new Kysely({
|
|
64
|
+
dialect: new PostgresDialect({
|
|
65
|
+
pool: new Pool({
|
|
66
|
+
host: 'localhost',
|
|
67
|
+
database: 'auth',
|
|
68
|
+
}),
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await up(db);
|
|
73
|
+
const adapter = new SqlAdapter(db, { dialect: 'postgres' });
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### MySQL
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { Kysely, MysqlDialect } from 'kysely';
|
|
80
|
+
import { createPool } from 'mysql2';
|
|
81
|
+
import { SqlAdapter, up } from '@fortressauth/adapter-sql';
|
|
82
|
+
|
|
83
|
+
const db = new Kysely({
|
|
84
|
+
dialect: new MysqlDialect({
|
|
85
|
+
pool: createPool({
|
|
86
|
+
host: 'localhost',
|
|
87
|
+
database: 'auth',
|
|
88
|
+
}),
|
|
89
|
+
}),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await up(db);
|
|
93
|
+
const adapter = new SqlAdapter(db, { dialect: 'mysql' });
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Database Schema
|
|
97
|
+
|
|
98
|
+
The adapter creates four tables:
|
|
99
|
+
|
|
100
|
+
- **users**: User accounts with email verification and lockout support
|
|
101
|
+
- **accounts**: Provider-specific accounts (email, OAuth)
|
|
102
|
+
- **sessions**: Active user sessions with expiration
|
|
103
|
+
- **login_attempts**: Login attempt history for rate limiting
|
|
104
|
+
|
|
105
|
+
## Migrations
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { up, down } from '@fortressauth/adapter-sql';
|
|
109
|
+
|
|
110
|
+
// Apply migrations
|
|
111
|
+
await up(db);
|
|
112
|
+
|
|
113
|
+
// Rollback migrations
|
|
114
|
+
await down(db);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Transactions
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
await adapter.transaction(async (txRepo) => {
|
|
121
|
+
const user = User.create('user@example.com');
|
|
122
|
+
await txRepo.createUser(user);
|
|
123
|
+
|
|
124
|
+
const account = Account.createEmailAccount(
|
|
125
|
+
user.id,
|
|
126
|
+
'user@example.com',
|
|
127
|
+
passwordHash
|
|
128
|
+
);
|
|
129
|
+
await txRepo.createAccount(account);
|
|
130
|
+
});
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Account, type AuthRepository, type LoginAttempt, type Result, Session, User } from '@fortressauth/core';
|
|
2
|
+
import type { Kysely } from 'kysely';
|
|
3
|
+
import type { Database } from './schema.js';
|
|
4
|
+
export type DatabaseDialect = 'sqlite' | 'postgres' | 'mysql';
|
|
5
|
+
export interface SqlAdapterOptions {
|
|
6
|
+
/** Database dialect for proper type handling. Defaults to 'sqlite'. */
|
|
7
|
+
dialect?: DatabaseDialect;
|
|
8
|
+
}
|
|
9
|
+
export declare class SqlAdapter implements AuthRepository {
|
|
10
|
+
private readonly db;
|
|
11
|
+
private readonly dialect;
|
|
12
|
+
constructor(db: Kysely<Database>, options?: SqlAdapterOptions);
|
|
13
|
+
private isSqlite;
|
|
14
|
+
private parseBoolean;
|
|
15
|
+
private serializeBoolean;
|
|
16
|
+
private parseDate;
|
|
17
|
+
private serializeDate;
|
|
18
|
+
findUserByEmail(email: string): Promise<User | null>;
|
|
19
|
+
findUserById(id: string): Promise<User | null>;
|
|
20
|
+
createUser(user: User): Promise<Result<void, 'EMAIL_EXISTS'>>;
|
|
21
|
+
updateUser(user: User): Promise<void>;
|
|
22
|
+
findAccountByProvider(providerId: string, providerUserId: string): Promise<Account | null>;
|
|
23
|
+
findEmailAccountByUserId(userId: string): Promise<Account | null>;
|
|
24
|
+
createAccount(account: Account): Promise<void>;
|
|
25
|
+
findSessionByTokenHash(tokenHash: string): Promise<Session | null>;
|
|
26
|
+
createSession(session: Session): Promise<void>;
|
|
27
|
+
deleteSession(sessionId: string): Promise<void>;
|
|
28
|
+
deleteSessionsByUserId(userId: string): Promise<void>;
|
|
29
|
+
recordLoginAttempt(attempt: LoginAttempt): Promise<void>;
|
|
30
|
+
countRecentFailedAttempts(email: string, windowMs: number): Promise<number>;
|
|
31
|
+
transaction<T>(fn: (repo: AuthRepository) => Promise<T>): Promise<T>;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,KAAK,cAAc,EAEnB,KAAK,YAAY,EAEjB,KAAK,MAAM,EACX,OAAO,EACP,IAAI,EACL,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;AAE9D,MAAM,WAAW,iBAAiB;IAChC,uEAAuE;IACvE,OAAO,CAAC,EAAE,eAAe,CAAC;CAC3B;AAED,qBAAa,UAAW,YAAW,cAAc;IAI7C,OAAO,CAAC,QAAQ,CAAC,EAAE;IAHrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;gBAGvB,EAAE,EAAE,MAAM,CAAC,QAAQ,CAAC,EACrC,OAAO,GAAE,iBAAsB;IAKjC,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,aAAa;IAIf,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAqBpD,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAqB9C,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IA6B7D,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAarC,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAsB1F,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAsBjE,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAc9C,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAsBlE,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAe9C,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrD,kBAAkB,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAcxD,yBAAyB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAgB3E,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAQ3E"}
|
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { Account, err, ok, Session, User, } from '@fortressauth/core';
|
|
2
|
+
export class SqlAdapter {
|
|
3
|
+
db;
|
|
4
|
+
dialect;
|
|
5
|
+
constructor(db, options = {}) {
|
|
6
|
+
this.db = db;
|
|
7
|
+
this.dialect = options.dialect ?? 'sqlite';
|
|
8
|
+
}
|
|
9
|
+
isSqlite() {
|
|
10
|
+
return this.dialect === 'sqlite';
|
|
11
|
+
}
|
|
12
|
+
parseBoolean(value) {
|
|
13
|
+
return typeof value === 'boolean' ? value : value === 1;
|
|
14
|
+
}
|
|
15
|
+
serializeBoolean(value) {
|
|
16
|
+
return this.isSqlite() ? (value ? 1 : 0) : value;
|
|
17
|
+
}
|
|
18
|
+
parseDate(value) {
|
|
19
|
+
return typeof value === 'string' ? new Date(value) : value;
|
|
20
|
+
}
|
|
21
|
+
serializeDate(value) {
|
|
22
|
+
return this.isSqlite() ? value.toISOString() : value;
|
|
23
|
+
}
|
|
24
|
+
async findUserByEmail(email) {
|
|
25
|
+
const row = await this.db
|
|
26
|
+
.selectFrom('users')
|
|
27
|
+
.selectAll()
|
|
28
|
+
.where('email', '=', email.toLowerCase())
|
|
29
|
+
.executeTakeFirst();
|
|
30
|
+
if (!row) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return User.rehydrate({
|
|
34
|
+
id: row.id,
|
|
35
|
+
email: row.email,
|
|
36
|
+
emailVerified: this.parseBoolean(row.email_verified),
|
|
37
|
+
createdAt: this.parseDate(row.created_at),
|
|
38
|
+
updatedAt: this.parseDate(row.updated_at),
|
|
39
|
+
lockedUntil: row.locked_until ? this.parseDate(row.locked_until) : null,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async findUserById(id) {
|
|
43
|
+
const row = await this.db
|
|
44
|
+
.selectFrom('users')
|
|
45
|
+
.selectAll()
|
|
46
|
+
.where('id', '=', id)
|
|
47
|
+
.executeTakeFirst();
|
|
48
|
+
if (!row) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return User.rehydrate({
|
|
52
|
+
id: row.id,
|
|
53
|
+
email: row.email,
|
|
54
|
+
emailVerified: this.parseBoolean(row.email_verified),
|
|
55
|
+
createdAt: this.parseDate(row.created_at),
|
|
56
|
+
updatedAt: this.parseDate(row.updated_at),
|
|
57
|
+
lockedUntil: row.locked_until ? this.parseDate(row.locked_until) : null,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async createUser(user) {
|
|
61
|
+
try {
|
|
62
|
+
await this.db
|
|
63
|
+
.insertInto('users')
|
|
64
|
+
.values({
|
|
65
|
+
id: user.id,
|
|
66
|
+
email: user.email,
|
|
67
|
+
email_verified: this.serializeBoolean(user.emailVerified),
|
|
68
|
+
created_at: this.serializeDate(user.createdAt),
|
|
69
|
+
updated_at: this.serializeDate(user.updatedAt),
|
|
70
|
+
locked_until: user.lockedUntil ? this.serializeDate(user.lockedUntil) : null,
|
|
71
|
+
})
|
|
72
|
+
.execute();
|
|
73
|
+
return ok(undefined);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
// Check for unique constraint violation across different database drivers
|
|
77
|
+
const isUniqueViolation = error instanceof Error &&
|
|
78
|
+
(('code' in error && (error.code === 'SQLITE_CONSTRAINT' || error.code === '23505')) ||
|
|
79
|
+
error.message?.includes('UNIQUE constraint failed') ||
|
|
80
|
+
error.message?.includes('duplicate key'));
|
|
81
|
+
if (isUniqueViolation) {
|
|
82
|
+
return err('EMAIL_EXISTS');
|
|
83
|
+
}
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async updateUser(user) {
|
|
88
|
+
await this.db
|
|
89
|
+
.updateTable('users')
|
|
90
|
+
.set({
|
|
91
|
+
email: user.email,
|
|
92
|
+
email_verified: this.serializeBoolean(user.emailVerified),
|
|
93
|
+
updated_at: this.serializeDate(user.updatedAt),
|
|
94
|
+
locked_until: user.lockedUntil ? this.serializeDate(user.lockedUntil) : null,
|
|
95
|
+
})
|
|
96
|
+
.where('id', '=', user.id)
|
|
97
|
+
.execute();
|
|
98
|
+
}
|
|
99
|
+
async findAccountByProvider(providerId, providerUserId) {
|
|
100
|
+
const row = await this.db
|
|
101
|
+
.selectFrom('accounts')
|
|
102
|
+
.selectAll()
|
|
103
|
+
.where('provider_id', '=', providerId)
|
|
104
|
+
.where('provider_user_id', '=', providerUserId)
|
|
105
|
+
.executeTakeFirst();
|
|
106
|
+
if (!row) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return Account.rehydrate({
|
|
110
|
+
id: row.id,
|
|
111
|
+
userId: row.user_id,
|
|
112
|
+
providerId: row.provider_id,
|
|
113
|
+
providerUserId: row.provider_user_id,
|
|
114
|
+
passwordHash: row.password_hash,
|
|
115
|
+
createdAt: this.parseDate(row.created_at),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
async findEmailAccountByUserId(userId) {
|
|
119
|
+
const row = await this.db
|
|
120
|
+
.selectFrom('accounts')
|
|
121
|
+
.selectAll()
|
|
122
|
+
.where('user_id', '=', userId)
|
|
123
|
+
.where('provider_id', '=', 'email')
|
|
124
|
+
.executeTakeFirst();
|
|
125
|
+
if (!row) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
return Account.rehydrate({
|
|
129
|
+
id: row.id,
|
|
130
|
+
userId: row.user_id,
|
|
131
|
+
providerId: row.provider_id,
|
|
132
|
+
providerUserId: row.provider_user_id,
|
|
133
|
+
passwordHash: row.password_hash,
|
|
134
|
+
createdAt: this.parseDate(row.created_at),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
async createAccount(account) {
|
|
138
|
+
await this.db
|
|
139
|
+
.insertInto('accounts')
|
|
140
|
+
.values({
|
|
141
|
+
id: account.id,
|
|
142
|
+
user_id: account.userId,
|
|
143
|
+
provider_id: account.providerId,
|
|
144
|
+
provider_user_id: account.providerUserId,
|
|
145
|
+
password_hash: account.passwordHash,
|
|
146
|
+
created_at: this.serializeDate(account.createdAt),
|
|
147
|
+
})
|
|
148
|
+
.execute();
|
|
149
|
+
}
|
|
150
|
+
async findSessionByTokenHash(tokenHash) {
|
|
151
|
+
const row = await this.db
|
|
152
|
+
.selectFrom('sessions')
|
|
153
|
+
.selectAll()
|
|
154
|
+
.where('token_hash', '=', tokenHash)
|
|
155
|
+
.executeTakeFirst();
|
|
156
|
+
if (!row) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
return Session.rehydrate({
|
|
160
|
+
id: row.id,
|
|
161
|
+
userId: row.user_id,
|
|
162
|
+
tokenHash: row.token_hash,
|
|
163
|
+
expiresAt: this.parseDate(row.expires_at),
|
|
164
|
+
ipAddress: row.ip_address,
|
|
165
|
+
userAgent: row.user_agent,
|
|
166
|
+
createdAt: this.parseDate(row.created_at),
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
async createSession(session) {
|
|
170
|
+
await this.db
|
|
171
|
+
.insertInto('sessions')
|
|
172
|
+
.values({
|
|
173
|
+
id: session.id,
|
|
174
|
+
user_id: session.userId,
|
|
175
|
+
token_hash: session.tokenHash,
|
|
176
|
+
expires_at: this.serializeDate(session.expiresAt),
|
|
177
|
+
ip_address: session.ipAddress,
|
|
178
|
+
user_agent: session.userAgent,
|
|
179
|
+
created_at: this.serializeDate(session.createdAt),
|
|
180
|
+
})
|
|
181
|
+
.execute();
|
|
182
|
+
}
|
|
183
|
+
async deleteSession(sessionId) {
|
|
184
|
+
await this.db.deleteFrom('sessions').where('id', '=', sessionId).execute();
|
|
185
|
+
}
|
|
186
|
+
async deleteSessionsByUserId(userId) {
|
|
187
|
+
await this.db.deleteFrom('sessions').where('user_id', '=', userId).execute();
|
|
188
|
+
}
|
|
189
|
+
async recordLoginAttempt(attempt) {
|
|
190
|
+
await this.db
|
|
191
|
+
.insertInto('login_attempts')
|
|
192
|
+
.values({
|
|
193
|
+
id: attempt.id,
|
|
194
|
+
user_id: attempt.userId,
|
|
195
|
+
email: attempt.email,
|
|
196
|
+
ip_address: attempt.ipAddress,
|
|
197
|
+
success: this.serializeBoolean(attempt.success),
|
|
198
|
+
created_at: this.serializeDate(attempt.createdAt),
|
|
199
|
+
})
|
|
200
|
+
.execute();
|
|
201
|
+
}
|
|
202
|
+
async countRecentFailedAttempts(email, windowMs) {
|
|
203
|
+
const cutoffDate = new Date(Date.now() - windowMs);
|
|
204
|
+
// For SQLite, we need to compare as ISO string; for other DBs, use Date
|
|
205
|
+
const cutoffValue = this.isSqlite() ? cutoffDate.toISOString() : cutoffDate;
|
|
206
|
+
const result = await this.db
|
|
207
|
+
.selectFrom('login_attempts')
|
|
208
|
+
.select((eb) => eb.fn.countAll().as('count'))
|
|
209
|
+
.where('email', '=', email.toLowerCase())
|
|
210
|
+
.where('success', '=', this.serializeBoolean(false))
|
|
211
|
+
.where('created_at', '>=', cutoffValue)
|
|
212
|
+
.executeTakeFirst();
|
|
213
|
+
return Number(result?.count ?? 0);
|
|
214
|
+
}
|
|
215
|
+
async transaction(fn) {
|
|
216
|
+
return await this.db.transaction().execute(async (trx) => {
|
|
217
|
+
const txAdapter = new SqlAdapter(trx, {
|
|
218
|
+
dialect: this.dialect,
|
|
219
|
+
});
|
|
220
|
+
return await fn(txAdapter);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAC;AACvD,YAAY,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,6BAA6B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"001_initial.d.ts","sourceRoot":"","sources":["../../src/migrations/001_initial.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGrC,wBAAsB,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CA8DxD;AAED,wBAAsB,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAK1D"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Generic type parameter allows any database schema since migrations only use schema builder
|
|
2
|
+
export async function up(db) {
|
|
3
|
+
await db.schema
|
|
4
|
+
.createTable('users')
|
|
5
|
+
.addColumn('id', 'text', (col) => col.primaryKey())
|
|
6
|
+
.addColumn('email', 'text', (col) => col.notNull().unique())
|
|
7
|
+
.addColumn('email_verified', 'boolean', (col) => col.notNull().defaultTo(false))
|
|
8
|
+
.addColumn('created_at', 'timestamp', (col) => col.notNull())
|
|
9
|
+
.addColumn('updated_at', 'timestamp', (col) => col.notNull())
|
|
10
|
+
.addColumn('locked_until', 'timestamp')
|
|
11
|
+
.execute();
|
|
12
|
+
// Note: email already has a unique constraint which creates an implicit index
|
|
13
|
+
await db.schema
|
|
14
|
+
.createTable('accounts')
|
|
15
|
+
.addColumn('id', 'text', (col) => col.primaryKey())
|
|
16
|
+
.addColumn('user_id', 'text', (col) => col.notNull().references('users.id').onDelete('cascade'))
|
|
17
|
+
.addColumn('provider_id', 'text', (col) => col.notNull())
|
|
18
|
+
.addColumn('provider_user_id', 'text', (col) => col.notNull())
|
|
19
|
+
.addColumn('password_hash', 'text')
|
|
20
|
+
.addColumn('created_at', 'timestamp', (col) => col.notNull())
|
|
21
|
+
.execute();
|
|
22
|
+
await db.schema
|
|
23
|
+
.createIndex('accounts_provider_idx')
|
|
24
|
+
.on('accounts')
|
|
25
|
+
.columns(['provider_id', 'provider_user_id'])
|
|
26
|
+
.unique()
|
|
27
|
+
.execute();
|
|
28
|
+
await db.schema.createIndex('accounts_user_id_idx').on('accounts').column('user_id').execute();
|
|
29
|
+
await db.schema
|
|
30
|
+
.createTable('sessions')
|
|
31
|
+
.addColumn('id', 'text', (col) => col.primaryKey())
|
|
32
|
+
.addColumn('user_id', 'text', (col) => col.notNull().references('users.id').onDelete('cascade'))
|
|
33
|
+
.addColumn('token_hash', 'text', (col) => col.notNull().unique())
|
|
34
|
+
.addColumn('expires_at', 'timestamp', (col) => col.notNull())
|
|
35
|
+
.addColumn('ip_address', 'text')
|
|
36
|
+
.addColumn('user_agent', 'text')
|
|
37
|
+
.addColumn('created_at', 'timestamp', (col) => col.notNull())
|
|
38
|
+
.execute();
|
|
39
|
+
// Note: token_hash already has a unique constraint which creates an implicit index
|
|
40
|
+
await db.schema.createIndex('sessions_user_id_idx').on('sessions').column('user_id').execute();
|
|
41
|
+
await db.schema
|
|
42
|
+
.createTable('login_attempts')
|
|
43
|
+
.addColumn('id', 'text', (col) => col.primaryKey())
|
|
44
|
+
.addColumn('user_id', 'text', (col) => col.references('users.id').onDelete('set null'))
|
|
45
|
+
.addColumn('email', 'text', (col) => col.notNull())
|
|
46
|
+
.addColumn('ip_address', 'text', (col) => col.notNull())
|
|
47
|
+
.addColumn('success', 'boolean', (col) => col.notNull())
|
|
48
|
+
.addColumn('created_at', 'timestamp', (col) => col.notNull())
|
|
49
|
+
.execute();
|
|
50
|
+
await db.schema
|
|
51
|
+
.createIndex('login_attempts_email_created_idx')
|
|
52
|
+
.on('login_attempts')
|
|
53
|
+
.columns(['email', 'created_at'])
|
|
54
|
+
.execute();
|
|
55
|
+
}
|
|
56
|
+
export async function down(db) {
|
|
57
|
+
await db.schema.dropTable('login_attempts').execute();
|
|
58
|
+
await db.schema.dropTable('sessions').execute();
|
|
59
|
+
await db.schema.dropTable('accounts').execute();
|
|
60
|
+
await db.schema.dropTable('users').execute();
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=001_initial.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"001_initial.js","sourceRoot":"","sources":["../../src/migrations/001_initial.ts"],"names":[],"mappings":"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"}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ColumnType } from 'kysely';
|
|
2
|
+
export interface UsersTable {
|
|
3
|
+
id: string;
|
|
4
|
+
email: string;
|
|
5
|
+
email_verified: number | boolean;
|
|
6
|
+
created_at: ColumnType<Date, Date | string, Date | string>;
|
|
7
|
+
updated_at: ColumnType<Date, Date | string, Date | string>;
|
|
8
|
+
locked_until: ColumnType<Date | null, Date | string | null, Date | string | null>;
|
|
9
|
+
}
|
|
10
|
+
export interface AccountsTable {
|
|
11
|
+
id: string;
|
|
12
|
+
user_id: string;
|
|
13
|
+
provider_id: string;
|
|
14
|
+
provider_user_id: string;
|
|
15
|
+
password_hash: string | null;
|
|
16
|
+
created_at: ColumnType<Date, Date | string, Date | string>;
|
|
17
|
+
}
|
|
18
|
+
export interface SessionsTable {
|
|
19
|
+
id: string;
|
|
20
|
+
user_id: string;
|
|
21
|
+
token_hash: string;
|
|
22
|
+
expires_at: ColumnType<Date, Date | string, Date | string>;
|
|
23
|
+
ip_address: string | null;
|
|
24
|
+
user_agent: string | null;
|
|
25
|
+
created_at: ColumnType<Date, Date | string, Date | string>;
|
|
26
|
+
}
|
|
27
|
+
export interface LoginAttemptsTable {
|
|
28
|
+
id: string;
|
|
29
|
+
user_id: string | null;
|
|
30
|
+
email: string;
|
|
31
|
+
ip_address: string;
|
|
32
|
+
success: number | boolean;
|
|
33
|
+
created_at: ColumnType<Date, Date | string, Date | string>;
|
|
34
|
+
}
|
|
35
|
+
export interface Database {
|
|
36
|
+
users: UsersTable;
|
|
37
|
+
accounts: AccountsTable;
|
|
38
|
+
sessions: SessionsTable;
|
|
39
|
+
login_attempts: LoginAttemptsTable;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEzC,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,UAAU,EAAE,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC;IAC3D,UAAU,EAAE,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC;IAC3D,YAAY,EAAE,UAAU,CAAC,IAAI,GAAG,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC,CAAC;CACnF;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC;IAC3D,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,UAAU,EAAE,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,IAAI,GAAG,MAAM,CAAC,CAAC;CAC5D;AAED,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,UAAU,CAAC;IAClB,QAAQ,EAAE,aAAa,CAAC;IACxB,QAAQ,EAAE,aAAa,CAAC;IACxB,cAAc,EAAE,kBAAkB,CAAC;CACpC"}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fortressauth/adapter-sql",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"kysely": "^0.28.8",
|
|
15
|
+
"@fortressauth/core": "0.1.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@biomejs/biome": "^2.3.8",
|
|
19
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
20
|
+
"@vitest/coverage-v8": "^4.0.15",
|
|
21
|
+
"better-sqlite3": "^12.5.0",
|
|
22
|
+
"typescript": "^5.9.3",
|
|
23
|
+
"vitest": "^4.0.15"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"test": "vitest run",
|
|
28
|
+
"test:watch": "vitest",
|
|
29
|
+
"test:coverage": "vitest run --coverage",
|
|
30
|
+
"lint": "biome check .",
|
|
31
|
+
"typecheck": "tsc --noEmit"
|
|
32
|
+
}
|
|
33
|
+
}
|