Skip to content

Commit d113cd1

Browse files
Copilothotlong
andcommitted
Create plugin-better-auth package with core authentication functionality
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 9f5ce5c commit d113cd1

File tree

7 files changed

+575
-4
lines changed

7 files changed

+575
-4
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# @objectos/plugin-better-auth
2+
3+
Authentication plugin for ObjectOS based on [Better-Auth](https://www.better-auth.com/) library.
4+
5+
## Features
6+
7+
- 🔐 **Email/Password Authentication** - Built-in email and password authentication
8+
- 🏢 **Organization Management** - Multi-tenant organization support with teams
9+
- 👥 **Role-Based Access Control (RBAC)** - Flexible role and permission system
10+
- 💾 **Multi-Database Support** - Works with PostgreSQL, MongoDB, and SQLite
11+
- 🔌 **Plugin Architecture** - Follows ObjectOS plugin lifecycle and conventions
12+
13+
## Installation
14+
15+
This plugin is part of the ObjectOS monorepo. It's already available in the workspace.
16+
17+
```bash
18+
pnpm install
19+
```
20+
21+
## Usage
22+
23+
### Basic Usage
24+
25+
```typescript
26+
import { ObjectOS } from '@objectos/kernel';
27+
import { BetterAuthPlugin } from '@objectos/plugin-better-auth';
28+
29+
const os = new ObjectOS({
30+
plugins: [BetterAuthPlugin],
31+
// ... other config
32+
});
33+
34+
await os.init();
35+
```
36+
37+
### Custom Configuration
38+
39+
```typescript
40+
import { createBetterAuthPlugin } from '@objectos/plugin-better-auth';
41+
42+
const customAuthPlugin = createBetterAuthPlugin({
43+
databaseUrl: 'postgres://localhost:5432/mydb',
44+
baseURL: 'https://myapp.com/api/auth',
45+
trustedOrigins: ['https://myapp.com', 'https://app.myapp.com']
46+
});
47+
48+
const os = new ObjectOS({
49+
plugins: [customAuthPlugin],
50+
// ... other config
51+
});
52+
```
53+
54+
## API Endpoints
55+
56+
Once enabled, the plugin registers the following authentication endpoints:
57+
58+
- `POST /api/auth/sign-up` - User registration
59+
- `POST /api/auth/sign-in/email` - Email/password login
60+
- `POST /api/auth/sign-out` - Logout
61+
- `GET /api/auth/get-session` - Get current session
62+
- And more... (see [Better-Auth documentation](https://www.better-auth.com/docs))
63+
64+
## Configuration Options
65+
66+
### BetterAuthPluginOptions
67+
68+
| Option | Type | Default | Description |
69+
|--------|------|---------|-------------|
70+
| `databaseUrl` | `string` | `process.env.OBJECTQL_DATABASE_URL` | Database connection string |
71+
| `baseURL` | `string` | `http://localhost:3000/api/auth` | Base URL for authentication endpoints |
72+
| `trustedOrigins` | `string[]` | `['http://localhost:5173', 'http://localhost:3000']` | Allowed origins for CORS |
73+
74+
## Database Support
75+
76+
The plugin automatically detects the database type from the connection string:
77+
78+
- **PostgreSQL**: `postgres://...` or `postgresql://...`
79+
- **MongoDB**: `mongodb://...`
80+
- **SQLite**: `sqlite:...` or any other (default)
81+
82+
### First User Setup
83+
84+
The first user to register automatically receives the `super_admin` role. Subsequent users get the `user` role by default.
85+
86+
## Organization & Roles
87+
88+
The plugin includes Better-Auth's organization plugin with the following default roles:
89+
90+
- **owner** - Full organization control
91+
- **admin** - Organization management (cannot delete organization)
92+
- **user** - Read-only access
93+
94+
## Plugin Lifecycle
95+
96+
The plugin implements all standard ObjectOS plugin lifecycle hooks:
97+
98+
- `onInstall` - Stores installation metadata
99+
- `onEnable` - Initializes Better-Auth and registers routes
100+
- `onDisable` - Gracefully disables authentication (preserves data)
101+
- `onUninstall` - Cleans up plugin storage (preserves user data)
102+
103+
## Events
104+
105+
The plugin contributes the following events to the ObjectOS event system:
106+
107+
- `auth.user.created` - Fired when a new user is created
108+
- `auth.user.login` - Fired when a user logs in
109+
- `auth.user.logout` - Fired when a user logs out
110+
- `auth.session.created` - Fired when a new session is created
111+
- `auth.session.expired` - Fired when a session expires
112+
113+
## Security
114+
115+
- First user automatically becomes `super_admin`
116+
- Supports CORS with configurable trusted origins
117+
- Session-based authentication with secure cookies
118+
- Database hooks for user creation validation
119+
120+
## License
121+
122+
AGPL-3.0
123+
124+
## Related
125+
126+
- [Better-Auth Documentation](https://www.better-auth.com/docs)
127+
- [ObjectOS Documentation](../../README.md)
128+
- [@objectstack/spec](https://github.com/objectstack-ai/spec)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "@objectos/plugin-better-auth",
3+
"version": "0.1.0",
4+
"license": "AGPL-3.0",
5+
"description": "Better-Auth authentication plugin for ObjectOS",
6+
"main": "dist/index.js",
7+
"types": "dist/index.d.ts",
8+
"scripts": {
9+
"build": "tsc",
10+
"test": "jest --passWithNoTests"
11+
},
12+
"dependencies": {
13+
"@objectstack/spec": "0.6.0",
14+
"better-auth": "^1.4.10",
15+
"better-sqlite3": "^12.6.0",
16+
"mongodb": "^7.0.0",
17+
"pg": "^8.11.3"
18+
},
19+
"devDependencies": {
20+
"@types/node": "^20.10.0",
21+
"@types/pg": "^8.11.0",
22+
"@types/better-sqlite3": "^7.6.0",
23+
"typescript": "^5.9.3"
24+
},
25+
"peerDependencies": {
26+
"@objectql/core": "^3.0.1"
27+
}
28+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/**
2+
* Better-Auth Client Configuration
3+
*
4+
* This module provides the Better-Auth instance configuration
5+
* with support for multiple database backends (PostgreSQL, MongoDB, SQLite)
6+
*/
7+
8+
let authInstance: any;
9+
10+
export interface BetterAuthConfig {
11+
databaseUrl?: string;
12+
baseURL?: string;
13+
trustedOrigins?: string[];
14+
}
15+
16+
export const getBetterAuth = async (config: BetterAuthConfig = {}) => {
17+
if (authInstance) return authInstance;
18+
19+
const { betterAuth } = await import("better-auth");
20+
const { organization } = await import("better-auth/plugins");
21+
const { role } = await import("better-auth/plugins/access");
22+
23+
try {
24+
let database;
25+
const dbUrl = config.databaseUrl || process.env.OBJECTQL_DATABASE_URL;
26+
const isPostgres = dbUrl && dbUrl.startsWith('postgres');
27+
const isMongo = dbUrl && dbUrl.startsWith('mongodb');
28+
29+
// Initialize database connection based on database type
30+
if (isPostgres) {
31+
const { Pool } = await import("pg");
32+
database = new Pool({
33+
connectionString: dbUrl!
34+
});
35+
} else if (isMongo) {
36+
const { MongoClient } = await import("mongodb");
37+
const client = new MongoClient(dbUrl!);
38+
await client.connect();
39+
database = client.db();
40+
} else {
41+
const sqlite3Import = await import("better-sqlite3");
42+
// Handle both ESM/Interop (default export) and CJS (direct export)
43+
const Database = (sqlite3Import.default || sqlite3Import) as any;
44+
const filename = (dbUrl && dbUrl.replace('sqlite:', '')) ? (dbUrl && dbUrl.replace('sqlite:', '')) : 'objectos.db';
45+
console.log(`[Better-Auth Plugin] Initializing with SQLite database: ${filename}`);
46+
database = new Database(filename);
47+
}
48+
49+
// Configure Better-Auth with organization and role plugins
50+
authInstance = betterAuth({
51+
database: database,
52+
baseURL: config.baseURL || process.env.BETTER_AUTH_URL || "http://localhost:3000/api/auth",
53+
trustedOrigins: config.trustedOrigins || [
54+
"http://localhost:5173",
55+
"http://localhost:3000",
56+
"http://[::1]:3000",
57+
"http://[::1]:5173"
58+
],
59+
emailAndPassword: {
60+
enabled: true
61+
},
62+
user: {
63+
additionalFields: {
64+
role: {
65+
type: "string",
66+
required: false,
67+
defaultValue: 'user',
68+
input: false
69+
}
70+
}
71+
},
72+
databaseHooks: {
73+
user: {
74+
create: {
75+
before: async (user) => {
76+
try {
77+
let count = 0;
78+
79+
// Get user count to determine if this is the first user (admin)
80+
if (isPostgres) {
81+
const result = await database.query('SELECT count(*) FROM "user"');
82+
count = parseInt(result.rows[0].count);
83+
} else if (isMongo) {
84+
const collection = database.collection('user');
85+
count = await collection.countDocuments();
86+
} else {
87+
try {
88+
const stmt = database.prepare('SELECT count(*) as count FROM user');
89+
const result = stmt.get() as any;
90+
count = result.count;
91+
} catch {
92+
count = 0;
93+
}
94+
}
95+
96+
// First user gets super_admin role
97+
const role = count === 0 ? 'super_admin' : 'user';
98+
console.log(`[Better-Auth Plugin] Creating user with role: ${role} (current count: ${count})`);
99+
100+
return {
101+
data: {
102+
...user,
103+
role
104+
}
105+
};
106+
} catch (e) {
107+
console.error("[Better-Auth Plugin] Error in user create hook:", e);
108+
return { data: user };
109+
}
110+
}
111+
}
112+
}
113+
},
114+
plugins: [
115+
organization({
116+
dynamicAccessControl: {
117+
enabled: true
118+
},
119+
teams: {
120+
enabled: true
121+
},
122+
creatorRole: 'owner',
123+
roles: {
124+
owner: role({
125+
organization: ['update', 'delete', 'read'],
126+
member: ['create', 'update', 'delete', 'read'],
127+
invitation: ['create', 'cancel', 'read'],
128+
team: ['create', 'update', 'delete', 'read']
129+
}),
130+
admin: role({
131+
organization: ['update', 'read'],
132+
member: ['create', 'update', 'delete', 'read'],
133+
invitation: ['create', 'cancel', 'read'],
134+
team: ['create', 'update', 'delete', 'read']
135+
}),
136+
user: role({
137+
organization: ['read'],
138+
member: ['read'],
139+
team: ['read']
140+
})
141+
}
142+
})
143+
]
144+
});
145+
146+
console.log('[Better-Auth Plugin] Initialized successfully');
147+
return authInstance;
148+
} catch (e: any) {
149+
console.error("[Better-Auth Plugin] Initialization Error:", e);
150+
throw e;
151+
}
152+
};
153+
154+
export const resetAuthInstance = () => {
155+
authInstance = undefined;
156+
};
157+
158+
export default { getBetterAuth, resetAuthInstance };
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* @objectos/plugin-better-auth
3+
*
4+
* Authentication plugin for ObjectOS based on Better-Auth library
5+
*
6+
* @example
7+
* ```typescript
8+
* import { BetterAuthPlugin, createBetterAuthPlugin } from '@objectos/plugin-better-auth';
9+
*
10+
* // Use default plugin
11+
* const os = new ObjectOS({
12+
* plugins: [BetterAuthPlugin]
13+
* });
14+
*
15+
* // Or create with custom configuration
16+
* const customAuthPlugin = createBetterAuthPlugin({
17+
* databaseUrl: 'postgres://localhost:5432/mydb',
18+
* baseURL: 'https://myapp.com/api/auth',
19+
* trustedOrigins: ['https://myapp.com']
20+
* });
21+
* ```
22+
*/
23+
24+
export {
25+
BetterAuthPlugin,
26+
BetterAuthManifest,
27+
createBetterAuthPlugin,
28+
getBetterAuth,
29+
type BetterAuthPluginOptions,
30+
type BetterAuthConfig,
31+
} from './plugin';
32+
33+
export { resetAuthInstance } from './auth-client';

0 commit comments

Comments
 (0)