@owox/idp-better-auth 0.5.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/README.md +183 -0
- package/dist/adapters/database.d.ts +29 -0
- package/dist/adapters/database.d.ts.map +1 -0
- package/dist/adapters/database.js +85 -0
- package/dist/auth/auth-config.d.ts +4 -0
- package/dist/auth/auth-config.d.ts.map +1 -0
- package/dist/auth/auth-config.js +72 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/providers/better-auth-provider.d.ts +34 -0
- package/dist/providers/better-auth-provider.d.ts.map +1 -0
- package/dist/providers/better-auth-provider.js +99 -0
- package/dist/services/authentication-service.d.ts +23 -0
- package/dist/services/authentication-service.d.ts.map +1 -0
- package/dist/services/authentication-service.js +199 -0
- package/dist/services/crypto-service.d.ts +18 -0
- package/dist/services/crypto-service.d.ts.map +1 -0
- package/dist/services/crypto-service.js +100 -0
- package/dist/services/database-service.d.ts +14 -0
- package/dist/services/database-service.d.ts.map +1 -0
- package/dist/services/database-service.js +142 -0
- package/dist/services/magic-link-service.d.ts +10 -0
- package/dist/services/magic-link-service.d.ts.map +1 -0
- package/dist/services/magic-link-service.js +42 -0
- package/dist/services/middleware-service.d.ts +15 -0
- package/dist/services/middleware-service.d.ts.map +1 -0
- package/dist/services/middleware-service.js +38 -0
- package/dist/services/page-service.d.ts +23 -0
- package/dist/services/page-service.d.ts.map +1 -0
- package/dist/services/page-service.js +331 -0
- package/dist/services/request-handler-service.d.ts +10 -0
- package/dist/services/request-handler-service.d.ts.map +1 -0
- package/dist/services/request-handler-service.js +50 -0
- package/dist/services/template-service.d.ts +28 -0
- package/dist/services/template-service.d.ts.map +1 -0
- package/dist/services/template-service.js +198 -0
- package/dist/services/token-service.d.ts +15 -0
- package/dist/services/token-service.d.ts.map +1 -0
- package/dist/services/token-service.js +108 -0
- package/dist/services/user-management-service.d.ts +74 -0
- package/dist/services/user-management-service.d.ts.map +1 -0
- package/dist/services/user-management-service.js +396 -0
- package/dist/templates/admin-add-user.html +298 -0
- package/dist/templates/admin-dashboard.html +214 -0
- package/dist/templates/admin-user-details.html +278 -0
- package/dist/templates/password-setup.html +267 -0
- package/dist/templates/password-success.html +197 -0
- package/dist/templates/sign-in.html +201 -0
- package/dist/types/auth-session.d.ts +25 -0
- package/dist/types/auth-session.d.ts.map +1 -0
- package/dist/types/auth-session.js +1 -0
- package/dist/types/database-models.d.ts +54 -0
- package/dist/types/database-models.d.ts.map +1 -0
- package/dist/types/database-models.js +1 -0
- package/dist/types/index.d.ts +45 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/package.json +69 -0
package/README.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# @owox/idp-better-auth
|
|
2
|
+
|
|
3
|
+
Better Auth IDP provider for OWOX Data Marts authentication.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
### 1. Environment Configuration
|
|
8
|
+
|
|
9
|
+
Create or update your `.env` file with the required settings:
|
|
10
|
+
|
|
11
|
+
```env
|
|
12
|
+
# Required
|
|
13
|
+
IDP_PROVIDER=better-auth
|
|
14
|
+
IDP_BETTER_AUTH_SECRET=your-super-secret-key-at-least-32-characters-long
|
|
15
|
+
|
|
16
|
+
# Database (SQLite - recommended for getting started)
|
|
17
|
+
IDP_BETTER_AUTH_DATABASE_TYPE=sqlite
|
|
18
|
+
IDP_BETTER_AUTH_SQLITE_DB_PATH=./data/auth.db
|
|
19
|
+
|
|
20
|
+
# Optional
|
|
21
|
+
IDP_BETTER_AUTH_BASE_URL=http://localhost:3000
|
|
22
|
+
IDP_BETTER_AUTH_MAGIC_LINK_TTL=3600
|
|
23
|
+
IDP_BETTER_AUTH_SESSION_MAX_AGE=86400
|
|
24
|
+
IDP_BETTER_AUTH_TRUSTED_ORIGINS=http://localhost:3000,http://localhost:3001
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
#### Tip
|
|
28
|
+
|
|
29
|
+
To generate a random secret key, you can use the following command:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
openssl rand -base64 32
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. MySQL Configuration (Alternative)
|
|
36
|
+
|
|
37
|
+
If you prefer MySQL instead of SQLite:
|
|
38
|
+
|
|
39
|
+
```env
|
|
40
|
+
IDP_PROVIDER=better-auth
|
|
41
|
+
IDP_BETTER_AUTH_SECRET=your-super-secret-key-at-least-32-characters-long
|
|
42
|
+
|
|
43
|
+
IDP_BETTER_AUTH_DATABASE_TYPE=mysql
|
|
44
|
+
IDP_BETTER_AUTH_MYSQL_HOST=localhost
|
|
45
|
+
IDP_BETTER_AUTH_MYSQL_USER=root
|
|
46
|
+
IDP_BETTER_AUTH_MYSQL_PASSWORD=your-password
|
|
47
|
+
IDP_BETTER_AUTH_MYSQL_DATABASE=owox_auth
|
|
48
|
+
IDP_BETTER_AUTH_MYSQL_PORT=3306
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3. Start the Application
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
owox serve
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
or if .env file doesn't exported:
|
|
58
|
+
|
|
59
|
+
**Linux/macOS:**
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
export $(grep -v '^#' .env | grep -v '^$' | xargs) && owox serve
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Windows (PowerShell):**
|
|
66
|
+
|
|
67
|
+
```powershell
|
|
68
|
+
Get-Content .env | Where-Object {$_ -notmatch '^#' -and $_ -notmatch '^$'} | ForEach-Object {$name, $value = $_.split('=', 2); Set-Variable -Name $name -Value $value}; owox serve
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Windows (Command Prompt):**
|
|
72
|
+
|
|
73
|
+
```cmd
|
|
74
|
+
for /f "usebackq tokens=1,2 delims==" %i in (.env) do set %i=%j
|
|
75
|
+
owox serve
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The authentication system will be available at:
|
|
79
|
+
|
|
80
|
+
- Sign in page: `http://localhost:3000/auth/sign-in`
|
|
81
|
+
- Admin dashboard: `http://localhost:3000/auth/dashboard`
|
|
82
|
+
|
|
83
|
+
### 4. Add Your First Admin User
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Add an admin user and get a magic link
|
|
87
|
+
export $(grep -v '^#' .env | grep -v '^$' | xargs) && owox idp add-user admin@yourdomain.com
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Copy the magic link from the output and open it in your browser to set up your admin account.
|
|
91
|
+
|
|
92
|
+
## Authentication Flow
|
|
93
|
+
|
|
94
|
+
### For End Users
|
|
95
|
+
|
|
96
|
+
1. **Sign In**: Navigate to `/auth/sign-in`
|
|
97
|
+
2. **Enter credentials**: Email and password
|
|
98
|
+
3. **Dashboard access**: After login, access admin features at `/auth/dashboard`
|
|
99
|
+
|
|
100
|
+
### For New Users (Admin Invitation)
|
|
101
|
+
|
|
102
|
+
1. **Admin invites**: Admin uses the dashboard to invite new users via magic link
|
|
103
|
+
2. **Magic link**: New user receives a magic link from user who invited them
|
|
104
|
+
3. **Password setup**: User goes to magic link and sets their password
|
|
105
|
+
4. **Access granted**: User can now sign in normally
|
|
106
|
+
|
|
107
|
+
## Admin Panel Features
|
|
108
|
+
|
|
109
|
+
The admin dashboard (`/auth/dashboard`) provides:
|
|
110
|
+
|
|
111
|
+
- **User management**: View all users, their roles, and activity
|
|
112
|
+
- **Add users**: Invite new users with specific roles (admin/editor/viewer)
|
|
113
|
+
- **Role management**: Assign different permission levels
|
|
114
|
+
- **Magic links**: Generate new magic links for existing users
|
|
115
|
+
|
|
116
|
+
### User Roles
|
|
117
|
+
|
|
118
|
+
- **Admin**: Full access, can manage all users and invite any role
|
|
119
|
+
- **Editor**: Can invite editors and viewers
|
|
120
|
+
- **Viewer**: Can only invite other viewers
|
|
121
|
+
|
|
122
|
+
## Database Management
|
|
123
|
+
|
|
124
|
+
The database schema is automatically created on first startup. For SQLite, the file will be created at the path specified in `IDP_BETTER_AUTH_SQLITE_DB_PATH` or default path in system application temp directory.
|
|
125
|
+
|
|
126
|
+
### SQLite (Default)
|
|
127
|
+
|
|
128
|
+
- File-based database
|
|
129
|
+
- No additional setup required
|
|
130
|
+
- Good for development and small deployments
|
|
131
|
+
|
|
132
|
+
### MySQL
|
|
133
|
+
|
|
134
|
+
- Requires MySQL server
|
|
135
|
+
- Create database manually: `CREATE DATABASE owox_auth;`
|
|
136
|
+
- Create user if needed: `CREATE USER 'owox_auth_user'@'localhost' IDENTIFIED BY 'your_password';`
|
|
137
|
+
- Grant privileges: `GRANT ALL PRIVILEGES ON owox_auth.* TO 'owox_auth_user'@'localhost';`
|
|
138
|
+
- Flush privileges: `FLUSH PRIVILEGES;`
|
|
139
|
+
- Tables are created automatically
|
|
140
|
+
|
|
141
|
+
## Command Line Tools
|
|
142
|
+
|
|
143
|
+
### Add User
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
owox idp add-user user@example.com
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Configuration Reference
|
|
150
|
+
|
|
151
|
+
| Variable | Required | Default | Description |
|
|
152
|
+
| --------------------------------- | :------: | :-----------------------: | ------------------------------------------- |
|
|
153
|
+
| `IDP_PROVIDER` | **Yes** | – | Set to `better-auth` |
|
|
154
|
+
| `IDP_BETTER_AUTH_SECRET` | **Yes** | – | Secret key for signing (min. 32 characters) |
|
|
155
|
+
| `IDP_BETTER_AUTH_DATABASE_TYPE` | No | `sqlite` | Database type: `sqlite` or `mysql` |
|
|
156
|
+
| `IDP_BETTER_AUTH_SQLITE_DB_PATH` | No | `<system temp directory>` | SQLite database file path |
|
|
157
|
+
| `IDP_BETTER_AUTH_BASE_URL` | No | `http://localhost:3000` | Base URL for magic links |
|
|
158
|
+
| `IDP_BETTER_AUTH_MAGIC_LINK_TTL` | No | `3600` (1 hour) | Magic link expiration (seconds) |
|
|
159
|
+
| `IDP_BETTER_AUTH_SESSION_MAX_AGE` | No | `604800` (7 days) | Session duration (seconds) |
|
|
160
|
+
| `IDP_BETTER_AUTH_MYSQL_HOST` | No | `localhost` | MySQL host |
|
|
161
|
+
| `IDP_BETTER_AUTH_MYSQL_USER` | No | `root` | MySQL user |
|
|
162
|
+
| `IDP_BETTER_AUTH_MYSQL_PASSWORD` | No | `your-password` | MySQL password |
|
|
163
|
+
| `IDP_BETTER_AUTH_MYSQL_DATABASE` | No | `owox_auth` | MySQL database |
|
|
164
|
+
| `IDP_BETTER_AUTH_MYSQL_PORT` | No | `3306` | MySQL port |
|
|
165
|
+
| `IDP_BETTER_AUTH_TRUSTED_ORIGINS` | No | `http://localhost:3000` | Trusted origins for auth service |
|
|
166
|
+
|
|
167
|
+
## Troubleshooting
|
|
168
|
+
|
|
169
|
+
### "IDP_BETTER_AUTH_SECRET is not set" Error
|
|
170
|
+
|
|
171
|
+
Make sure your `.env` file contains a valid `IDP_BETTER_AUTH_SECRET` with at least 32 characters.
|
|
172
|
+
|
|
173
|
+
### Database Connection Issues
|
|
174
|
+
|
|
175
|
+
For MySQL, verify your connection settings and ensure the database exists.
|
|
176
|
+
|
|
177
|
+
### Magic Links Not Working
|
|
178
|
+
|
|
179
|
+
Check that `IDP_BETTER_AUTH_BASE_URL` matches your application URL and that the magic link hasn't expired.
|
|
180
|
+
|
|
181
|
+
### Permission Denied
|
|
182
|
+
|
|
183
|
+
Ensure the user has the correct role permissions for the action they're trying to perform.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { DatabaseConfig, SqliteConfig, MySqlConfig } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Determines the SQLite database file path for IDP based on configuration.
|
|
4
|
+
*
|
|
5
|
+
* If `IDP_BETTER_AUTH_SQLITE_DB_PATH` environment variable is set, uses that path directly.
|
|
6
|
+
* Otherwise, uses cross-platform application data directory with 'owox/idp/auth.db' structure.
|
|
7
|
+
* Automatically creates the database directory if it doesn't exist.
|
|
8
|
+
*
|
|
9
|
+
* @returns The absolute path to the SQLite database file for IDP
|
|
10
|
+
* @throws {Error} When database directory cannot be created due to permissions or other filesystem errors
|
|
11
|
+
*/
|
|
12
|
+
declare function getIdpSqliteDatabasePath(): string;
|
|
13
|
+
/**
|
|
14
|
+
* Create SQLite database adapter for Better Auth
|
|
15
|
+
*/
|
|
16
|
+
export declare function createSqliteAdapter(config: SqliteConfig): Promise<unknown>;
|
|
17
|
+
/**
|
|
18
|
+
* Create MySQL database adapter for Better Auth
|
|
19
|
+
*/
|
|
20
|
+
export declare function createMysqlAdapter(config: MySqlConfig): Promise<unknown>;
|
|
21
|
+
/**
|
|
22
|
+
* Create database adapter based on configuration
|
|
23
|
+
*/
|
|
24
|
+
export declare function createDatabaseAdapter(config: DatabaseConfig): Promise<unknown>;
|
|
25
|
+
/**
|
|
26
|
+
* Export the IDP SQLite database path function for external use
|
|
27
|
+
*/
|
|
28
|
+
export { getIdpSqliteDatabasePath };
|
|
29
|
+
//# sourceMappingURL=database.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/adapters/database.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAKnF;;;;;;;;;GASG;AACH,iBAAS,wBAAwB,IAAI,MAAM,CA6B1C;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAQhF;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAiB9E;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CASpF;AAED;;GAEG;AACH,OAAO,EAAE,wBAAwB,EAAE,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import envPaths from 'env-paths';
|
|
2
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
/**
|
|
5
|
+
* Determines the SQLite database file path for IDP based on configuration.
|
|
6
|
+
*
|
|
7
|
+
* If `IDP_BETTER_AUTH_SQLITE_DB_PATH` environment variable is set, uses that path directly.
|
|
8
|
+
* Otherwise, uses cross-platform application data directory with 'owox/idp/auth.db' structure.
|
|
9
|
+
* Automatically creates the database directory if it doesn't exist.
|
|
10
|
+
*
|
|
11
|
+
* @returns The absolute path to the SQLite database file for IDP
|
|
12
|
+
* @throws {Error} When database directory cannot be created due to permissions or other filesystem errors
|
|
13
|
+
*/
|
|
14
|
+
function getIdpSqliteDatabasePath() {
|
|
15
|
+
const envDbPath = process.env.IDP_BETTER_AUTH_SQLITE_DB_PATH;
|
|
16
|
+
let dbPath;
|
|
17
|
+
if (envDbPath) {
|
|
18
|
+
console.log(`Using IDP SQLite database path from \`IDP_BETTER_AUTH_SQLITE_DB_PATH\` env: ${envDbPath}`);
|
|
19
|
+
dbPath = envDbPath;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const paths = envPaths('owox', { suffix: '' });
|
|
23
|
+
dbPath = join(paths.data, 'idp', 'auth.db');
|
|
24
|
+
console.log(`Using system app data directory for IDP SQLite: ${dbPath}`);
|
|
25
|
+
}
|
|
26
|
+
const dbDir = dirname(dbPath);
|
|
27
|
+
if (!existsSync(dbDir)) {
|
|
28
|
+
try {
|
|
29
|
+
mkdirSync(dbDir, { recursive: true });
|
|
30
|
+
console.log(`Created IDP SQLite database directory: ${dbDir}`);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw new Error(`Failed to create IDP SQLite database directory: ${dbDir}. ${error instanceof Error ? error.message : String(error)}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return dbPath;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Create SQLite database adapter for Better Auth
|
|
40
|
+
*/
|
|
41
|
+
export async function createSqliteAdapter(config) {
|
|
42
|
+
// Dynamic import to handle optional dependency
|
|
43
|
+
const { default: Database } = await import('better-sqlite3');
|
|
44
|
+
// Use configured path or default to smart path resolution
|
|
45
|
+
const dbPath = config.filename || getIdpSqliteDatabasePath();
|
|
46
|
+
return new Database(dbPath);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Create MySQL database adapter for Better Auth
|
|
50
|
+
*/
|
|
51
|
+
export async function createMysqlAdapter(config) {
|
|
52
|
+
try {
|
|
53
|
+
const mysql = await import('mysql2/promise');
|
|
54
|
+
return mysql.default.createPool({
|
|
55
|
+
host: config.host,
|
|
56
|
+
user: config.user,
|
|
57
|
+
password: config.password,
|
|
58
|
+
database: config.database,
|
|
59
|
+
port: config.port || 3306,
|
|
60
|
+
waitForConnections: true,
|
|
61
|
+
connectionLimit: 10,
|
|
62
|
+
queueLimit: 0,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
throw new Error('mysql2 is required for MySQL support. Install it with: npm install mysql2');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Create database adapter based on configuration
|
|
71
|
+
*/
|
|
72
|
+
export async function createDatabaseAdapter(config) {
|
|
73
|
+
switch (config.type) {
|
|
74
|
+
case 'sqlite':
|
|
75
|
+
return await createSqliteAdapter(config);
|
|
76
|
+
case 'mysql':
|
|
77
|
+
return await createMysqlAdapter(config);
|
|
78
|
+
default:
|
|
79
|
+
throw new Error(`Unsupported database type: ${config.type}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Export the IDP SQLite database path function for external use
|
|
84
|
+
*/
|
|
85
|
+
export { getIdpSqliteDatabasePath };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-config.d.ts","sourceRoot":"","sources":["../../src/auth/auth-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAIrD,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,CAmGxC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { betterAuth } from 'better-auth';
|
|
2
|
+
import { magicLink, organization } from 'better-auth/plugins';
|
|
3
|
+
import { createDatabaseAdapter } from '../adapters/database.js';
|
|
4
|
+
import { createAccessControl } from 'better-auth/plugins/access';
|
|
5
|
+
export async function createBetterAuthConfig(config) {
|
|
6
|
+
const database = await createDatabaseAdapter(config.database);
|
|
7
|
+
const plugins = [];
|
|
8
|
+
plugins.push(magicLink({
|
|
9
|
+
sendMagicLink: async ({ email, token, url }) => {
|
|
10
|
+
global.lastMagicLink = url;
|
|
11
|
+
global.lastEmail = email;
|
|
12
|
+
global.lastToken = token;
|
|
13
|
+
},
|
|
14
|
+
expiresIn: 3600, // 1 hour
|
|
15
|
+
disableSignUp: false,
|
|
16
|
+
}));
|
|
17
|
+
const ac = createAccessControl({
|
|
18
|
+
project: ['create', 'update', 'delete', 'view'],
|
|
19
|
+
});
|
|
20
|
+
const adminRole = ac.newRole({
|
|
21
|
+
project: ['create', 'update', 'delete', 'view'],
|
|
22
|
+
});
|
|
23
|
+
const editorRole = ac.newRole({
|
|
24
|
+
project: ['create', 'update', 'delete', 'view'],
|
|
25
|
+
});
|
|
26
|
+
const viewerRole = ac.newRole({
|
|
27
|
+
project: ['view'],
|
|
28
|
+
});
|
|
29
|
+
plugins.push(organization({
|
|
30
|
+
ac,
|
|
31
|
+
roles: {
|
|
32
|
+
admin: adminRole,
|
|
33
|
+
editor: editorRole,
|
|
34
|
+
viewer: viewerRole,
|
|
35
|
+
},
|
|
36
|
+
allowUserToCreateOrganization: false,
|
|
37
|
+
organizationLimit: 1,
|
|
38
|
+
async sendInvitationEmail(_data) {
|
|
39
|
+
return;
|
|
40
|
+
},
|
|
41
|
+
}));
|
|
42
|
+
const authConfig = {
|
|
43
|
+
database,
|
|
44
|
+
plugins,
|
|
45
|
+
session: {
|
|
46
|
+
expiresIn: config.session?.maxAge || 60 * 60 * 24 * 7,
|
|
47
|
+
updateAge: 60 * 60 * 24,
|
|
48
|
+
},
|
|
49
|
+
trustedOrigins: config.baseURL ? [config.baseURL] : ['http://localhost:3000'],
|
|
50
|
+
baseURL: config.baseURL || 'http://localhost:3000',
|
|
51
|
+
secret: config.secret,
|
|
52
|
+
emailAndPassword: {
|
|
53
|
+
enabled: true,
|
|
54
|
+
requireEmailVerification: false,
|
|
55
|
+
},
|
|
56
|
+
advanced: {
|
|
57
|
+
cookies: {
|
|
58
|
+
session_token: {
|
|
59
|
+
name: 'refreshToken',
|
|
60
|
+
attributes: {
|
|
61
|
+
httpOnly: true,
|
|
62
|
+
secure: config.baseURL?.includes('localhost') || config.baseURL?.includes('127.0.0.1')
|
|
63
|
+
? false
|
|
64
|
+
: true,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
basePath: '/auth/better-auth',
|
|
70
|
+
};
|
|
71
|
+
return betterAuth(authConfig);
|
|
72
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { BetterAuthProvider } from './providers/better-auth-provider.js';
|
|
2
|
+
export { createBetterAuthConfig } from './auth/auth-config.js';
|
|
3
|
+
export { createDatabaseAdapter, createSqliteAdapter, createMysqlAdapter, getIdpSqliteDatabasePath, } from './adapters/database.js';
|
|
4
|
+
export { TemplateService } from './services/template-service.js';
|
|
5
|
+
export { MagicLinkService } from './services/magic-link-service.js';
|
|
6
|
+
export { PageService } from './services/page-service.js';
|
|
7
|
+
export { AuthenticationService } from './services/authentication-service.js';
|
|
8
|
+
export { TokenService } from './services/token-service.js';
|
|
9
|
+
export { UserManagementService } from './services/user-management-service.js';
|
|
10
|
+
export { DatabaseService } from './services/database-service.js';
|
|
11
|
+
export { RequestHandlerService } from './services/request-handler-service.js';
|
|
12
|
+
export { MiddlewareService } from './services/middleware-service.js';
|
|
13
|
+
export { CryptoService } from './services/crypto-service.js';
|
|
14
|
+
export type { BetterAuthConfig, DatabaseConfig, SqliteConfig, MySqlConfig, CustomDatabaseConfig, } from './types/index.js';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAG/D,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,kBAAkB,EAClB,wBAAwB,GACzB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAC;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAG7D,YAAY,EACV,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,WAAW,EACX,oBAAoB,GACrB,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Main Better Auth exports
|
|
2
|
+
export { BetterAuthProvider } from './providers/better-auth-provider.js';
|
|
3
|
+
export { createBetterAuthConfig } from './auth/auth-config.js';
|
|
4
|
+
// Database adapters
|
|
5
|
+
export { createDatabaseAdapter, createSqliteAdapter, createMysqlAdapter, getIdpSqliteDatabasePath, } from './adapters/database.js';
|
|
6
|
+
// Services
|
|
7
|
+
export { TemplateService } from './services/template-service.js';
|
|
8
|
+
export { MagicLinkService } from './services/magic-link-service.js';
|
|
9
|
+
export { PageService } from './services/page-service.js';
|
|
10
|
+
export { AuthenticationService } from './services/authentication-service.js';
|
|
11
|
+
export { TokenService } from './services/token-service.js';
|
|
12
|
+
export { UserManagementService } from './services/user-management-service.js';
|
|
13
|
+
export { DatabaseService } from './services/database-service.js';
|
|
14
|
+
export { RequestHandlerService } from './services/request-handler-service.js';
|
|
15
|
+
export { MiddlewareService } from './services/middleware-service.js';
|
|
16
|
+
export { CryptoService } from './services/crypto-service.js';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AuthResult, AddUserCommandResponse, IdpProviderAddUserCommand, IdpProviderListUsersCommand, IdpProviderRemoveUserCommand, IdpProvider, Payload } from '@owox/idp-protocol';
|
|
2
|
+
import { betterAuth } from 'better-auth';
|
|
3
|
+
import { Express, type Request, Response, NextFunction } from 'express';
|
|
4
|
+
import { BetterAuthConfig } from '../types/index.js';
|
|
5
|
+
export declare class BetterAuthProvider implements IdpProvider, IdpProviderAddUserCommand, IdpProviderListUsersCommand, IdpProviderRemoveUserCommand {
|
|
6
|
+
private readonly auth;
|
|
7
|
+
private readonly authenticationService;
|
|
8
|
+
private readonly tokenService;
|
|
9
|
+
private readonly userManagementService;
|
|
10
|
+
private readonly databaseService;
|
|
11
|
+
private readonly requestHandlerService;
|
|
12
|
+
private readonly middlewareService;
|
|
13
|
+
private readonly pageService;
|
|
14
|
+
private constructor();
|
|
15
|
+
getAuth(): Awaited<ReturnType<typeof betterAuth>>;
|
|
16
|
+
static create(config: BetterAuthConfig): Promise<BetterAuthProvider>;
|
|
17
|
+
getBetterAuth(): Awaited<ReturnType<typeof betterAuth>>;
|
|
18
|
+
registerRoutes(app: Express): void;
|
|
19
|
+
signInMiddleware(req: Request, res: Response, next: NextFunction): Promise<void | Response>;
|
|
20
|
+
signOutMiddleware(req: Request, res: Response, next: NextFunction): Promise<void | Response>;
|
|
21
|
+
accessTokenMiddleware(req: Request, res: Response, next: NextFunction): Promise<void | Response>;
|
|
22
|
+
userApiMiddleware(req: Request, res: Response, next: NextFunction): Promise<Response<Payload>>;
|
|
23
|
+
initialize(): Promise<void>;
|
|
24
|
+
introspectToken(token: string): Promise<Payload | null>;
|
|
25
|
+
parseToken(token: string): Promise<Payload | null>;
|
|
26
|
+
verifyToken(token: string): Promise<Payload | null>;
|
|
27
|
+
refreshToken(refreshToken: string): Promise<AuthResult>;
|
|
28
|
+
revokeToken(token: string): Promise<void>;
|
|
29
|
+
shutdown(): Promise<void>;
|
|
30
|
+
addUser(username: string, _password?: string): Promise<AddUserCommandResponse>;
|
|
31
|
+
listUsers(): Promise<Payload[]>;
|
|
32
|
+
removeUser(userId: string): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=better-auth-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"better-auth-provider.d.ts","sourceRoot":"","sources":["../../src/providers/better-auth-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,sBAAsB,EACtB,yBAAyB,EACzB,2BAA2B,EAC3B,4BAA4B,EAC5B,WAAW,EACX,OAAO,EACR,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,KAAK,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAExE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAYrD,qBAAa,kBACX,YACE,WAAW,EACX,yBAAyB,EACzB,2BAA2B,EAC3B,4BAA4B;IAWV,OAAO,CAAC,QAAQ,CAAC,IAAI;IARzC,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAwB;IAC9D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAwB;IAC9D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAwB;IAC9D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAoB;IACtD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;IAE1C,OAAO;IA0BP,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;WAIpC,MAAM,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAK1E,aAAa,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;IAIvD,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI;IAe5B,gBAAgB,CACpB,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC;IAIrB,iBAAiB,CACrB,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC;IAIrB,qBAAqB,CACzB,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC;IAIrB,iBAAiB,CACrB,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAIvB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAIvD,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAIlD,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;IAInD,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAIvD,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIzC,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAIzB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAI9E,SAAS,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAI/B,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGhD"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { createBetterAuthConfig } from '../auth/auth-config.js';
|
|
3
|
+
import { MagicLinkService } from '../services/magic-link-service.js';
|
|
4
|
+
import { CryptoService } from '../services/crypto-service.js';
|
|
5
|
+
import { AuthenticationService } from '../services/authentication-service.js';
|
|
6
|
+
import { TokenService } from '../services/token-service.js';
|
|
7
|
+
import { UserManagementService } from '../services/user-management-service.js';
|
|
8
|
+
import { DatabaseService } from '../services/database-service.js';
|
|
9
|
+
import { RequestHandlerService } from '../services/request-handler-service.js';
|
|
10
|
+
import { MiddlewareService } from '../services/middleware-service.js';
|
|
11
|
+
import { PageService } from '../services/page-service.js';
|
|
12
|
+
export class BetterAuthProvider {
|
|
13
|
+
auth;
|
|
14
|
+
// Services
|
|
15
|
+
authenticationService;
|
|
16
|
+
tokenService;
|
|
17
|
+
userManagementService;
|
|
18
|
+
databaseService;
|
|
19
|
+
requestHandlerService;
|
|
20
|
+
middlewareService;
|
|
21
|
+
pageService;
|
|
22
|
+
constructor(auth) {
|
|
23
|
+
this.auth = auth;
|
|
24
|
+
// Initialize core services
|
|
25
|
+
const cryptoService = new CryptoService(this.auth);
|
|
26
|
+
const magicLinkService = new MagicLinkService(this.auth, cryptoService);
|
|
27
|
+
// Initialize all business logic services
|
|
28
|
+
this.authenticationService = new AuthenticationService(this.auth, cryptoService);
|
|
29
|
+
this.tokenService = new TokenService(this.auth, cryptoService);
|
|
30
|
+
this.userManagementService = new UserManagementService(this.auth, magicLinkService, cryptoService);
|
|
31
|
+
this.databaseService = new DatabaseService(this.auth);
|
|
32
|
+
this.requestHandlerService = new RequestHandlerService(this.auth);
|
|
33
|
+
this.pageService = new PageService(this.authenticationService, this.userManagementService, cryptoService);
|
|
34
|
+
this.middlewareService = new MiddlewareService(this.authenticationService, this.pageService);
|
|
35
|
+
// Set circular dependency
|
|
36
|
+
this.authenticationService.setUserManagementService(this.userManagementService);
|
|
37
|
+
}
|
|
38
|
+
getAuth() {
|
|
39
|
+
return this.auth;
|
|
40
|
+
}
|
|
41
|
+
static async create(config) {
|
|
42
|
+
const auth = await createBetterAuthConfig(config);
|
|
43
|
+
return new BetterAuthProvider(auth);
|
|
44
|
+
}
|
|
45
|
+
getBetterAuth() {
|
|
46
|
+
return this.auth;
|
|
47
|
+
}
|
|
48
|
+
registerRoutes(app) {
|
|
49
|
+
// Setup middleware
|
|
50
|
+
app.use(express.json()); // Add JSON parsing middleware
|
|
51
|
+
app.use(express.urlencoded({ extended: true }));
|
|
52
|
+
// Setup Better Auth handler
|
|
53
|
+
this.requestHandlerService.setupBetterAuthHandler(app);
|
|
54
|
+
this.pageService.registerRoutes(app);
|
|
55
|
+
app.post('/auth/api/sign-in', this.authenticationService.signInMiddleware.bind(this.authenticationService));
|
|
56
|
+
}
|
|
57
|
+
async signInMiddleware(req, res, next) {
|
|
58
|
+
return this.middlewareService.signInMiddleware(req, res, next);
|
|
59
|
+
}
|
|
60
|
+
async signOutMiddleware(req, res, next) {
|
|
61
|
+
return this.middlewareService.signOutMiddleware(req, res, next);
|
|
62
|
+
}
|
|
63
|
+
async accessTokenMiddleware(req, res, next) {
|
|
64
|
+
return this.middlewareService.accessTokenMiddleware(req, res, next);
|
|
65
|
+
}
|
|
66
|
+
async userApiMiddleware(req, res, next) {
|
|
67
|
+
return this.middlewareService.userApiMiddleware(req, res, next);
|
|
68
|
+
}
|
|
69
|
+
async initialize() {
|
|
70
|
+
await this.databaseService.runMigrations();
|
|
71
|
+
}
|
|
72
|
+
async introspectToken(token) {
|
|
73
|
+
return this.tokenService.introspectToken(token);
|
|
74
|
+
}
|
|
75
|
+
async parseToken(token) {
|
|
76
|
+
return this.tokenService.parseToken(token);
|
|
77
|
+
}
|
|
78
|
+
async verifyToken(token) {
|
|
79
|
+
return this.tokenService.verifyToken(token);
|
|
80
|
+
}
|
|
81
|
+
async refreshToken(refreshToken) {
|
|
82
|
+
return this.tokenService.refreshToken(refreshToken);
|
|
83
|
+
}
|
|
84
|
+
async revokeToken(token) {
|
|
85
|
+
return this.tokenService.revokeToken(token);
|
|
86
|
+
}
|
|
87
|
+
async shutdown() {
|
|
88
|
+
return Promise.resolve();
|
|
89
|
+
}
|
|
90
|
+
async addUser(username, _password) {
|
|
91
|
+
return this.userManagementService.addUserViaMagicLink(username);
|
|
92
|
+
}
|
|
93
|
+
async listUsers() {
|
|
94
|
+
return this.userManagementService.listUsers();
|
|
95
|
+
}
|
|
96
|
+
async removeUser(userId) {
|
|
97
|
+
return this.userManagementService.removeUser(userId);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createBetterAuthConfig } from '../auth/auth-config.js';
|
|
2
|
+
import { CryptoService } from './crypto-service.js';
|
|
3
|
+
import { AuthSession, SessionValidationResult } from '../types/auth-session.js';
|
|
4
|
+
import { type Request, type Response, type NextFunction } from 'express';
|
|
5
|
+
import type { UserManagementService } from './user-management-service.js';
|
|
6
|
+
export declare class AuthenticationService {
|
|
7
|
+
private readonly auth;
|
|
8
|
+
private readonly cryptoService;
|
|
9
|
+
private userManagementService?;
|
|
10
|
+
constructor(auth: Awaited<ReturnType<typeof createBetterAuthConfig>>, cryptoService: CryptoService);
|
|
11
|
+
setUserManagementService(userManagementService: UserManagementService): void;
|
|
12
|
+
getSession(req: Request): Promise<AuthSession | null>;
|
|
13
|
+
signIn(email: string, password: string, protocol: string, host: string): Promise<globalThis.Response>;
|
|
14
|
+
signOut(req: Request): Promise<void>;
|
|
15
|
+
generateAccessToken(req: Request): Promise<string>;
|
|
16
|
+
validateSession(req: Request): Promise<SessionValidationResult>;
|
|
17
|
+
signInMiddleware(req: Request, res: Response, _next: NextFunction): Promise<void | Response>;
|
|
18
|
+
signOutMiddleware(req: Request, res: Response, _next: NextFunction): Promise<void | Response>;
|
|
19
|
+
accessTokenMiddleware(req: Request, res: Response, _next: NextFunction): Promise<void | Response>;
|
|
20
|
+
setPassword(password: string, req: Request): Promise<unknown>;
|
|
21
|
+
requireAuthMiddleware(req: Request, res: Response, next: NextFunction): Promise<void | Response>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=authentication-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authentication-service.d.ts","sourceRoot":"","sources":["../../src/services/authentication-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAChF,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AACzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAE1E,qBAAa,qBAAqB;IAI9B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAJhC,OAAO,CAAC,qBAAqB,CAAC,CAAwB;gBAGnC,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC,EACxD,aAAa,EAAE,aAAa;IAG/C,wBAAwB,CAAC,qBAAqB,EAAE,qBAAqB,GAAG,IAAI;IAItE,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IA6BrD,MAAM,CACV,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC;IAoBzB,OAAO,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAWpC,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAyBlD,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,uBAAuB,CAAC;IAuB/D,gBAAgB,CACpB,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC;IAmCrB,iBAAiB,CACrB,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC;IAgBrB,qBAAqB,CACzB,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC;IAiBrB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAqB7D,qBAAqB,CACzB,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC;CAe5B"}
|