@mastra/auth-better-auth 1.1.2 → 1.1.3-alpha.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/CHANGELOG.md +7 -0
- package/dist/_types/@internal_auth/dist/_types/@internal_core/dist/base/index.d.ts +27 -27
- package/dist/_types/@internal_auth/dist/_types/@internal_core/dist/index-S1lgaKO7.d.ts +218 -0
- package/dist/_types/@internal_auth/dist/ee/interfaces/permissions.generated.d.ts +12 -2
- package/dist/index.cjs +742 -713
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +741 -711
- package/dist/index.js.map +1 -1
- package/package.json +10 -9
- package/dist/_types/@internal_auth/dist/_types/@internal_core/dist/logger/index.d.ts +0 -217
package/dist/index.js
CHANGED
|
@@ -1,727 +1,757 @@
|
|
|
1
|
-
import { LibsqlDialect } from
|
|
2
|
-
import { betterAuth } from
|
|
3
|
-
import { makeSignature } from
|
|
4
|
-
import { getMigrations } from
|
|
5
|
-
import { organization } from
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import { LibsqlDialect } from "@libsql/kysely-libsql";
|
|
2
|
+
import { betterAuth } from "better-auth";
|
|
3
|
+
import { makeSignature } from "better-auth/crypto";
|
|
4
|
+
import { getMigrations } from "better-auth/db/migration";
|
|
5
|
+
import { organization } from "better-auth/plugins";
|
|
6
|
+
//#region ../../packages/_internal-core/dist/logger/index.js
|
|
7
|
+
const RegisteredLogger = {
|
|
8
|
+
AGENT: "AGENT",
|
|
9
|
+
OBSERVABILITY: "OBSERVABILITY",
|
|
10
|
+
AUTH: "AUTH",
|
|
11
|
+
BROWSER: "BROWSER",
|
|
12
|
+
NETWORK: "NETWORK",
|
|
13
|
+
WORKFLOW: "WORKFLOW",
|
|
14
|
+
LLM: "LLM",
|
|
15
|
+
TTS: "TTS",
|
|
16
|
+
VOICE: "VOICE",
|
|
17
|
+
VECTOR: "VECTOR",
|
|
18
|
+
BUNDLER: "BUNDLER",
|
|
19
|
+
DEPLOYER: "DEPLOYER",
|
|
20
|
+
MEMORY: "MEMORY",
|
|
21
|
+
STORAGE: "STORAGE",
|
|
22
|
+
EMBEDDINGS: "EMBEDDINGS",
|
|
23
|
+
MCP_SERVER: "MCP_SERVER",
|
|
24
|
+
SERVER_CACHE: "SERVER_CACHE",
|
|
25
|
+
SERVER: "SERVER",
|
|
26
|
+
WORKSPACE: "WORKSPACE",
|
|
27
|
+
CHANNEL: "CHANNEL"
|
|
28
|
+
};
|
|
29
|
+
const LogLevel = {
|
|
30
|
+
DEBUG: "debug",
|
|
31
|
+
INFO: "info",
|
|
32
|
+
WARN: "warn",
|
|
33
|
+
ERROR: "error",
|
|
34
|
+
NONE: "silent"
|
|
35
|
+
};
|
|
15
36
|
var MastraLogger = class {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
37
|
+
name;
|
|
38
|
+
level;
|
|
39
|
+
transports;
|
|
40
|
+
constructor(options = {}) {
|
|
41
|
+
this.name = options.name || "Mastra";
|
|
42
|
+
this.level = options.level || LogLevel.ERROR;
|
|
43
|
+
this.transports = new Map(Object.entries(options.transports || {}));
|
|
44
|
+
}
|
|
45
|
+
getTransports() {
|
|
46
|
+
return this.transports;
|
|
47
|
+
}
|
|
48
|
+
trackException(_error, _metadata) {}
|
|
49
|
+
async listLogs(transportId, params) {
|
|
50
|
+
if (!transportId || !this.transports.has(transportId)) return {
|
|
51
|
+
logs: [],
|
|
52
|
+
total: 0,
|
|
53
|
+
page: params?.page ?? 1,
|
|
54
|
+
perPage: params?.perPage ?? 100,
|
|
55
|
+
hasMore: false
|
|
56
|
+
};
|
|
57
|
+
return this.transports.get(transportId).listLogs?.(params) ?? {
|
|
58
|
+
logs: [],
|
|
59
|
+
total: 0,
|
|
60
|
+
page: params?.page ?? 1,
|
|
61
|
+
perPage: params?.perPage ?? 100,
|
|
62
|
+
hasMore: false
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async listLogsByRunId({ transportId, runId, fromDate, toDate, logLevel, filters, page, perPage }) {
|
|
66
|
+
if (!transportId || !this.transports.has(transportId) || !runId) return {
|
|
67
|
+
logs: [],
|
|
68
|
+
total: 0,
|
|
69
|
+
page: page ?? 1,
|
|
70
|
+
perPage: perPage ?? 100,
|
|
71
|
+
hasMore: false
|
|
72
|
+
};
|
|
73
|
+
return this.transports.get(transportId).listLogsByRunId?.({
|
|
74
|
+
runId,
|
|
75
|
+
fromDate,
|
|
76
|
+
toDate,
|
|
77
|
+
logLevel,
|
|
78
|
+
filters,
|
|
79
|
+
page,
|
|
80
|
+
perPage
|
|
81
|
+
}) ?? {
|
|
82
|
+
logs: [],
|
|
83
|
+
total: 0,
|
|
84
|
+
page: page ?? 1,
|
|
85
|
+
perPage: perPage ?? 100,
|
|
86
|
+
hasMore: false
|
|
87
|
+
};
|
|
88
|
+
}
|
|
62
89
|
};
|
|
63
|
-
var ConsoleLogger = class
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
90
|
+
var ConsoleLogger = class ConsoleLogger extends MastraLogger {
|
|
91
|
+
component;
|
|
92
|
+
filter;
|
|
93
|
+
constructor(options = {}) {
|
|
94
|
+
super(options);
|
|
95
|
+
this.component = options.component;
|
|
96
|
+
this.filter = options.filter;
|
|
97
|
+
}
|
|
98
|
+
child(componentOrBindings) {
|
|
99
|
+
const component = typeof componentOrBindings === "string" ? componentOrBindings : componentOrBindings?.component ?? this.component;
|
|
100
|
+
return new ConsoleLogger({
|
|
101
|
+
name: this.name,
|
|
102
|
+
level: this.level,
|
|
103
|
+
component,
|
|
104
|
+
filter: this.filter
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
shouldLog(level, message, args) {
|
|
108
|
+
if (!this.filter) return true;
|
|
109
|
+
try {
|
|
110
|
+
return this.filter({
|
|
111
|
+
component: this.component,
|
|
112
|
+
level,
|
|
113
|
+
message,
|
|
114
|
+
args
|
|
115
|
+
});
|
|
116
|
+
} catch (e) {
|
|
117
|
+
console.error(`[Logger] Filter error for component=${this.component} level=${level}:`, e);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
prefix() {
|
|
122
|
+
return this.component ? `[${this.component}] ` : "";
|
|
123
|
+
}
|
|
124
|
+
debug(message, ...args) {
|
|
125
|
+
if (this.level === LogLevel.DEBUG && this.shouldLog(LogLevel.DEBUG, message, args)) console.info(`${this.prefix()}${message}`, ...args);
|
|
126
|
+
}
|
|
127
|
+
info(message, ...args) {
|
|
128
|
+
if ((this.level === LogLevel.INFO || this.level === LogLevel.DEBUG) && this.shouldLog(LogLevel.INFO, message, args)) console.info(`${this.prefix()}${message}`, ...args);
|
|
129
|
+
}
|
|
130
|
+
warn(message, ...args) {
|
|
131
|
+
if ((this.level === LogLevel.WARN || this.level === LogLevel.INFO || this.level === LogLevel.DEBUG) && this.shouldLog(LogLevel.WARN, message, args)) console.warn(`${this.prefix()}${message}`, ...args);
|
|
132
|
+
}
|
|
133
|
+
error(message, ...args) {
|
|
134
|
+
if ((this.level === LogLevel.ERROR || this.level === LogLevel.WARN || this.level === LogLevel.INFO || this.level === LogLevel.DEBUG) && this.shouldLog(LogLevel.ERROR, message, args)) console.error(`${this.prefix()}${message}`, ...args);
|
|
135
|
+
}
|
|
136
|
+
async listLogs(_transportId, _params) {
|
|
137
|
+
return {
|
|
138
|
+
logs: [],
|
|
139
|
+
total: 0,
|
|
140
|
+
page: _params?.page ?? 1,
|
|
141
|
+
perPage: _params?.perPage ?? 100,
|
|
142
|
+
hasMore: false
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
async listLogsByRunId(_args) {
|
|
146
|
+
return {
|
|
147
|
+
logs: [],
|
|
148
|
+
total: 0,
|
|
149
|
+
page: _args.page ?? 1,
|
|
150
|
+
perPage: _args.perPage ?? 100,
|
|
151
|
+
hasMore: false
|
|
152
|
+
};
|
|
153
|
+
}
|
|
118
154
|
};
|
|
119
|
-
|
|
120
|
-
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region ../../packages/_internal-core/dist/base/index.js
|
|
121
157
|
var MastraBase = class {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
*/
|
|
154
|
-
__setLogger(logger) {
|
|
155
|
-
this.logger = "child" in logger && typeof logger.child === "function" ? logger.child({ component: this.component }) : logger;
|
|
156
|
-
}
|
|
158
|
+
component = RegisteredLogger.LLM;
|
|
159
|
+
logger;
|
|
160
|
+
name;
|
|
161
|
+
#rawConfig;
|
|
162
|
+
constructor({ component, name, rawConfig }) {
|
|
163
|
+
this.component = component || RegisteredLogger.LLM;
|
|
164
|
+
this.name = name;
|
|
165
|
+
this.#rawConfig = rawConfig;
|
|
166
|
+
this.logger = new ConsoleLogger({ name: `${this.component} - ${this.name}` });
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Returns the raw storage configuration this primitive was created from,
|
|
170
|
+
* or undefined if it was created from code.
|
|
171
|
+
*/
|
|
172
|
+
toRawConfig() {
|
|
173
|
+
return this.#rawConfig;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Sets the raw storage configuration for this primitive.
|
|
177
|
+
* @internal
|
|
178
|
+
*/
|
|
179
|
+
__setRawConfig(rawConfig) {
|
|
180
|
+
this.#rawConfig = rawConfig;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Set the logger for the agent
|
|
184
|
+
* @param logger
|
|
185
|
+
*/
|
|
186
|
+
__setLogger(logger) {
|
|
187
|
+
this.logger = "child" in logger && typeof logger.child === "function" ? logger.child({ component: this.component }) : logger;
|
|
188
|
+
}
|
|
157
189
|
};
|
|
158
|
-
|
|
159
|
-
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region ../../packages/_internals/auth/dist/provider/index.js
|
|
160
192
|
var MastraAuthProvider = class extends MastraBase {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (opts?.protected) {
|
|
180
|
-
this.protected = opts.protected;
|
|
181
|
-
}
|
|
182
|
-
if (opts?.public) {
|
|
183
|
-
this.public = opts.public;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
193
|
+
protected;
|
|
194
|
+
public;
|
|
195
|
+
constructor(options) {
|
|
196
|
+
super({
|
|
197
|
+
component: "AUTH",
|
|
198
|
+
name: options?.name
|
|
199
|
+
});
|
|
200
|
+
if (options?.authorizeUser) this.authorizeUser = options.authorizeUser.bind(this);
|
|
201
|
+
this.protected = options?.protected;
|
|
202
|
+
this.public = options?.public;
|
|
203
|
+
this.mapUserToResourceId = options?.mapUserToResourceId;
|
|
204
|
+
}
|
|
205
|
+
registerOptions(opts) {
|
|
206
|
+
if (opts?.authorizeUser) this.authorizeUser = opts.authorizeUser.bind(this);
|
|
207
|
+
if (opts?.mapUserToResourceId) this.mapUserToResourceId = opts.mapUserToResourceId;
|
|
208
|
+
if (opts?.protected) this.protected = opts.protected;
|
|
209
|
+
if (opts?.public) this.public = opts.public;
|
|
210
|
+
}
|
|
186
211
|
};
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/index.ts
|
|
187
214
|
function tryDecode(value) {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
215
|
+
try {
|
|
216
|
+
return decodeURIComponent(value);
|
|
217
|
+
} catch {
|
|
218
|
+
return value;
|
|
219
|
+
}
|
|
193
220
|
}
|
|
194
|
-
function
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
return request.raw?.headers.get(name) ?? request.headers?.get(name) ?? request.header(name) ?? null;
|
|
221
|
+
function getRequestHeader(request, name) {
|
|
222
|
+
if (request instanceof Request) return request.headers.get(name);
|
|
223
|
+
return request.raw?.headers.get(name) ?? request.headers?.get(name) ?? request.header(name) ?? null;
|
|
199
224
|
}
|
|
225
|
+
/**
|
|
226
|
+
* Maps Better Auth User to EE User format.
|
|
227
|
+
*/
|
|
200
228
|
function mapBetterAuthUserToEEUser(user) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
229
|
+
return {
|
|
230
|
+
id: user.id,
|
|
231
|
+
email: user.email,
|
|
232
|
+
name: user.name,
|
|
233
|
+
avatarUrl: user.image ?? void 0,
|
|
234
|
+
metadata: {
|
|
235
|
+
emailVerified: user.emailVerified,
|
|
236
|
+
createdAt: user.createdAt,
|
|
237
|
+
updatedAt: user.updatedAt
|
|
238
|
+
}
|
|
239
|
+
};
|
|
212
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Mastra authentication provider for Better Auth.
|
|
243
|
+
*
|
|
244
|
+
* Better Auth is a self-hosted, open-source authentication framework
|
|
245
|
+
* that gives you full control over your authentication system.
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```typescript
|
|
249
|
+
* import { betterAuth } from 'better-auth';
|
|
250
|
+
* import { MastraAuthBetterAuth } from '@mastra/auth-better-auth';
|
|
251
|
+
*
|
|
252
|
+
* // Create your Better Auth instance
|
|
253
|
+
* const auth = betterAuth({
|
|
254
|
+
* database: {
|
|
255
|
+
* provider: 'postgresql',
|
|
256
|
+
* url: process.env.DATABASE_URL!,
|
|
257
|
+
* },
|
|
258
|
+
* emailAndPassword: {
|
|
259
|
+
* enabled: true,
|
|
260
|
+
* },
|
|
261
|
+
* });
|
|
262
|
+
*
|
|
263
|
+
* // Create the Mastra auth provider
|
|
264
|
+
* const mastraAuth = new MastraAuthBetterAuth({
|
|
265
|
+
* auth,
|
|
266
|
+
* });
|
|
267
|
+
*
|
|
268
|
+
* // Use with Mastra
|
|
269
|
+
* const mastra = new Mastra({
|
|
270
|
+
* server: {
|
|
271
|
+
* auth: mastraAuth,
|
|
272
|
+
* },
|
|
273
|
+
* });
|
|
274
|
+
* ```
|
|
275
|
+
*
|
|
276
|
+
* @see https://better-auth.com for Better Auth documentation
|
|
277
|
+
*/
|
|
213
278
|
var MastraAuthBetterAuth = class extends MastraAuthProvider {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
}
|
|
689
|
-
const cookies = [];
|
|
690
|
-
const setCookieHeader = response.headers.get("set-cookie");
|
|
691
|
-
if (setCookieHeader) {
|
|
692
|
-
cookies.push(...setCookieHeader.split(/,(?=\s*\w+=)/));
|
|
693
|
-
}
|
|
694
|
-
return {
|
|
695
|
-
user: mapBetterAuthUserToEEUser(result.user),
|
|
696
|
-
token: result.token ?? void 0,
|
|
697
|
-
cookies
|
|
698
|
-
};
|
|
699
|
-
}
|
|
700
|
-
/**
|
|
701
|
-
* Get the underlying Better Auth instance.
|
|
702
|
-
* Useful for accessing Better Auth APIs directly.
|
|
703
|
-
*/
|
|
704
|
-
getAuth() {
|
|
705
|
-
return this.auth;
|
|
706
|
-
}
|
|
707
|
-
/**
|
|
708
|
-
* Get headers to clear the session cookies on logout.
|
|
709
|
-
* Partial ISessionProvider implementation for logout support.
|
|
710
|
-
*
|
|
711
|
-
* Clears Better Auth's default session cookies.
|
|
712
|
-
*/
|
|
713
|
-
getClearSessionHeaders() {
|
|
714
|
-
const sameSite = this.#crossSite ? "None; Secure" : "Lax";
|
|
715
|
-
const cookies = [
|
|
716
|
-
`${this.sessionCookieName}=; Path=/; HttpOnly; SameSite=${sameSite}; Max-Age=0`,
|
|
717
|
-
`${this.sessionCookieName}_sig=; Path=/; HttpOnly; SameSite=${sameSite}; Max-Age=0`
|
|
718
|
-
];
|
|
719
|
-
return {
|
|
720
|
-
"Set-Cookie": cookies.join(", ")
|
|
721
|
-
};
|
|
722
|
-
}
|
|
279
|
+
#auth;
|
|
280
|
+
#secret;
|
|
281
|
+
/** True when `init()` built the instance — then we also own its migrations. */
|
|
282
|
+
#ownsInstance = false;
|
|
283
|
+
/** Once-per-process migration latch; reset on failure so a later call retries. */
|
|
284
|
+
#migrated;
|
|
285
|
+
/** In-process `userId → orgId` cache so hosts can call `ensureOrganization` per request. */
|
|
286
|
+
#orgCache = /* @__PURE__ */ new Map();
|
|
287
|
+
/** Set from `init()`: cross-origin SPA deploys need SameSite=None; Secure cookies. */
|
|
288
|
+
#crossSite = false;
|
|
289
|
+
signUpEnabledConfig;
|
|
290
|
+
constructor(options) {
|
|
291
|
+
super({ name: options?.name ?? "better-auth" });
|
|
292
|
+
if (!options.auth && !options.secret) throw new Error("Better Auth instance is required. Please provide the auth option with your Better Auth instance created via betterAuth({ ... }), or provide `secret` so the provider can build its own instance in init() on the host database.");
|
|
293
|
+
this.#auth = options.auth;
|
|
294
|
+
this.#secret = options.secret;
|
|
295
|
+
this.signUpEnabledConfig = options.signUpEnabled ?? true;
|
|
296
|
+
this.registerOptions(options);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* The active Better Auth instance. Throws before `init()` in deferred
|
|
300
|
+
* instance mode (constructed with `secret` instead of `auth`).
|
|
301
|
+
*/
|
|
302
|
+
get auth() {
|
|
303
|
+
if (!this.#auth) throw new Error("MastraAuthBetterAuth is not initialized — init() must run first (or pass a configured `auth` instance).");
|
|
304
|
+
return this.#auth;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Session cookie name, honoring Better Auth's `cookiePrefix`, a caller
|
|
308
|
+
* override via `advanced.cookies.session_token.name`, and the `__Secure-`
|
|
309
|
+
* prefix Better Auth applies when secure cookies are active.
|
|
310
|
+
*/
|
|
311
|
+
get sessionCookieName() {
|
|
312
|
+
const options = this.#auth?.options;
|
|
313
|
+
const advanced = options?.advanced;
|
|
314
|
+
const prefix = advanced?.cookiePrefix ?? "better-auth";
|
|
315
|
+
const secure = advanced?.useSecureCookies ?? options?.baseURL?.startsWith("https://") ?? false;
|
|
316
|
+
const baseName = advanced?.cookies?.session_token?.name ?? `${prefix}.session_token`;
|
|
317
|
+
return `${secure ? "__Secure-" : ""}${baseName}`;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* One-time host initialization. In deferred instance mode (no `auth` in the
|
|
321
|
+
* options) this builds the provider-owned `betterAuth()` instance on the
|
|
322
|
+
* host's auth database; schema migrations then run lazily behind a
|
|
323
|
+
* once-per-process latch on first use.
|
|
324
|
+
*
|
|
325
|
+
* Bring-your-own `auth` instances skip construction entirely — the caller
|
|
326
|
+
* owns their database and migrations.
|
|
327
|
+
*/
|
|
328
|
+
async init(ctx) {
|
|
329
|
+
this.#crossSite = (ctx.allowedOrigins?.length ?? 0) > 0;
|
|
330
|
+
if (this.#auth) return;
|
|
331
|
+
const authDb = ctx.database;
|
|
332
|
+
if (!authDb) throw new Error("MastraAuthBetterAuth needs a database to build its own better-auth instance, but the host passed none. Use a storage backend that exposes an auth database, or pass your own configured `auth` instance.");
|
|
333
|
+
const database = authDb.dialect === "postgres" ? authDb.pool : authDb.dialect === "libsql" ? {
|
|
334
|
+
dialect: new LibsqlDialect({ client: authDb.client }),
|
|
335
|
+
type: "sqlite"
|
|
336
|
+
} : authDb.database;
|
|
337
|
+
const allowedOrigins = ctx.allowedOrigins ?? [];
|
|
338
|
+
const options = {
|
|
339
|
+
database,
|
|
340
|
+
secret: this.#secret,
|
|
341
|
+
basePath: "/auth/api",
|
|
342
|
+
...ctx.publicUrl ? { baseURL: ctx.publicUrl } : {},
|
|
343
|
+
...allowedOrigins.length ? { trustedOrigins: allowedOrigins } : {},
|
|
344
|
+
emailAndPassword: {
|
|
345
|
+
enabled: true,
|
|
346
|
+
disableSignUp: !this.signUpEnabledConfig
|
|
347
|
+
},
|
|
348
|
+
plugins: [organization()],
|
|
349
|
+
...this.#crossSite ? { advanced: { defaultCookieAttributes: {
|
|
350
|
+
sameSite: "none",
|
|
351
|
+
secure: true
|
|
352
|
+
} } } : {}
|
|
353
|
+
};
|
|
354
|
+
this.#auth = betterAuth(options);
|
|
355
|
+
this.#ownsInstance = true;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Ensure better-auth's tables exist in the host database. Only for instances
|
|
359
|
+
* this provider built — bring-your-own instances manage their own migrations.
|
|
360
|
+
*/
|
|
361
|
+
async #ensureDbReady() {
|
|
362
|
+
if (!this.#ownsInstance) return;
|
|
363
|
+
this.#migrated ??= (async () => {
|
|
364
|
+
const { runMigrations } = await getMigrations(this.auth.options);
|
|
365
|
+
await runMigrations();
|
|
366
|
+
})();
|
|
367
|
+
try {
|
|
368
|
+
await this.#migrated;
|
|
369
|
+
} catch (error) {
|
|
370
|
+
this.#migrated = void 0;
|
|
371
|
+
console.warn("[BetterAuth] Failed to run auth schema migrations; auth stays unavailable until this succeeds.");
|
|
372
|
+
throw error;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Proxy a raw HTTP request to Better Auth's own API surface
|
|
377
|
+
* (sign-in/up/out/session). Hosts mount this under `/auth/api/*`.
|
|
378
|
+
*/
|
|
379
|
+
async handleAuthRequest(request) {
|
|
380
|
+
try {
|
|
381
|
+
await this.#ensureDbReady();
|
|
382
|
+
} catch {
|
|
383
|
+
return new Response(JSON.stringify({ error: "auth_unavailable" }), {
|
|
384
|
+
status: 503,
|
|
385
|
+
headers: { "content-type": "application/json" }
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
return this.auth.handler(request);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Ensure the user belongs to an organization, mirroring the WorkOS
|
|
392
|
+
* personal-org bootstrap on better-auth's organization tables:
|
|
393
|
+
* ≥1 membership → first org id; 0 → create a personal org with an
|
|
394
|
+
* idempotent slug derived from the user id.
|
|
395
|
+
*
|
|
396
|
+
* Concurrent/retried first logins recover via the unique slug instead of
|
|
397
|
+
* creating duplicates. Slug alone is NOT proof of ownership though: the
|
|
398
|
+
* organization API is reachable by any authenticated user, so an attacker
|
|
399
|
+
* could squat `personal-<victimId>`. The slug-matched org is only adopted
|
|
400
|
+
* when nobody else is a member; otherwise a fresh org with an unguessable
|
|
401
|
+
* slug is created instead.
|
|
402
|
+
*
|
|
403
|
+
* Best-effort: any failure is swallowed and leaves the user no-org.
|
|
404
|
+
*/
|
|
405
|
+
async ensureOrganization(userId) {
|
|
406
|
+
const cached = this.#orgCache.get(userId);
|
|
407
|
+
if (cached) return cached;
|
|
408
|
+
try {
|
|
409
|
+
await this.#ensureDbReady();
|
|
410
|
+
const ctx = await this.getAuthContext();
|
|
411
|
+
if (!ctx) return void 0;
|
|
412
|
+
const firstExisting = (await ctx.adapter.findMany({
|
|
413
|
+
model: "member",
|
|
414
|
+
where: [{
|
|
415
|
+
field: "userId",
|
|
416
|
+
value: userId
|
|
417
|
+
}]
|
|
418
|
+
})).find((m) => m.organizationId)?.organizationId;
|
|
419
|
+
if (firstExisting) {
|
|
420
|
+
this.#orgCache.set(userId, firstExisting);
|
|
421
|
+
return firstExisting;
|
|
422
|
+
}
|
|
423
|
+
const userRecord = await ctx.internalAdapter.findUserById(userId).catch(() => null);
|
|
424
|
+
const orgName = `${userRecord?.email ?? userRecord?.name ?? userId}'s org`;
|
|
425
|
+
const orgData = () => ({
|
|
426
|
+
name: orgName,
|
|
427
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
428
|
+
metadata: JSON.stringify({ mastraPersonalOrg: "true" })
|
|
429
|
+
});
|
|
430
|
+
const slug = `personal-${userId}`;
|
|
431
|
+
let organizationId;
|
|
432
|
+
try {
|
|
433
|
+
organizationId = (await ctx.adapter.create({
|
|
434
|
+
model: "organization",
|
|
435
|
+
data: {
|
|
436
|
+
...orgData(),
|
|
437
|
+
slug
|
|
438
|
+
}
|
|
439
|
+
})).id;
|
|
440
|
+
} catch (error) {
|
|
441
|
+
const existing = await ctx.adapter.findOne({
|
|
442
|
+
model: "organization",
|
|
443
|
+
where: [{
|
|
444
|
+
field: "slug",
|
|
445
|
+
value: slug
|
|
446
|
+
}]
|
|
447
|
+
});
|
|
448
|
+
if (!existing) throw error;
|
|
449
|
+
if ((await ctx.adapter.findMany({
|
|
450
|
+
model: "member",
|
|
451
|
+
where: [{
|
|
452
|
+
field: "organizationId",
|
|
453
|
+
value: existing.id
|
|
454
|
+
}]
|
|
455
|
+
})).some((m) => m.userId !== userId)) organizationId = (await ctx.adapter.create({
|
|
456
|
+
model: "organization",
|
|
457
|
+
data: {
|
|
458
|
+
...orgData(),
|
|
459
|
+
slug: `personal-${userId}-${crypto.randomUUID()}`
|
|
460
|
+
}
|
|
461
|
+
})).id;
|
|
462
|
+
else organizationId = existing.id;
|
|
463
|
+
}
|
|
464
|
+
try {
|
|
465
|
+
await ctx.adapter.create({
|
|
466
|
+
model: "member",
|
|
467
|
+
data: {
|
|
468
|
+
organizationId,
|
|
469
|
+
userId,
|
|
470
|
+
role: "owner",
|
|
471
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
} catch (error) {
|
|
475
|
+
if (!await ctx.adapter.findOne({
|
|
476
|
+
model: "member",
|
|
477
|
+
where: [{
|
|
478
|
+
field: "organizationId",
|
|
479
|
+
value: organizationId
|
|
480
|
+
}, {
|
|
481
|
+
field: "userId",
|
|
482
|
+
value: userId
|
|
483
|
+
}]
|
|
484
|
+
})) throw error;
|
|
485
|
+
}
|
|
486
|
+
this.#orgCache.set(userId, organizationId);
|
|
487
|
+
return organizationId;
|
|
488
|
+
} catch (error) {
|
|
489
|
+
console.warn(`[BetterAuth] Failed to bootstrap personal organization for user ${userId}. The user will see organization_required until this succeeds.`, error);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Whether the user holds an admin-equivalent role (`owner` or `admin`) in
|
|
495
|
+
* the organization. Provider errors resolve to `false`.
|
|
496
|
+
*/
|
|
497
|
+
async isOrganizationAdmin(organizationId, userId) {
|
|
498
|
+
try {
|
|
499
|
+
await this.#ensureDbReady();
|
|
500
|
+
const ctx = await this.getAuthContext();
|
|
501
|
+
if (!ctx) return false;
|
|
502
|
+
const membership = await ctx.adapter.findOne({
|
|
503
|
+
model: "member",
|
|
504
|
+
where: [{
|
|
505
|
+
field: "organizationId",
|
|
506
|
+
value: organizationId
|
|
507
|
+
}, {
|
|
508
|
+
field: "userId",
|
|
509
|
+
value: userId
|
|
510
|
+
}]
|
|
511
|
+
});
|
|
512
|
+
return membership?.role === "owner" || membership?.role === "admin";
|
|
513
|
+
} catch {
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Check if sign-up is enabled.
|
|
519
|
+
* Implements ICredentialsProvider.isSignUpEnabled.
|
|
520
|
+
*/
|
|
521
|
+
isSignUpEnabled() {
|
|
522
|
+
return this.signUpEnabledConfig;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Get current user from request.
|
|
526
|
+
* Implements IUserProvider for EE user awareness in Studio.
|
|
527
|
+
*
|
|
528
|
+
* Supports both cookie-based sessions and `Authorization: Bearer <token>`
|
|
529
|
+
* requests (e.g. API clients using the token returned by `signIn`).
|
|
530
|
+
*
|
|
531
|
+
* @param request - Incoming HTTP request
|
|
532
|
+
* @returns EE User object or null if not authenticated
|
|
533
|
+
*/
|
|
534
|
+
async getCurrentUser(request) {
|
|
535
|
+
try {
|
|
536
|
+
await this.#ensureDbReady();
|
|
537
|
+
const headers = new Headers(request.headers);
|
|
538
|
+
const authHeader = headers.get("Authorization");
|
|
539
|
+
const bearerToken = authHeader && authHeader.slice(0, 7).toLowerCase() === "bearer " ? authHeader.slice(7).trim() : void 0;
|
|
540
|
+
await this.ensureSessionCookie(headers, bearerToken);
|
|
541
|
+
const result = await this.auth.api.getSession({ headers });
|
|
542
|
+
if (!result?.user) return null;
|
|
543
|
+
return mapBetterAuthUserToEEUser(result.user);
|
|
544
|
+
} catch {
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Get user by ID.
|
|
550
|
+
* Implements IUserProvider for EE user awareness.
|
|
551
|
+
*
|
|
552
|
+
* Uses Better Auth's internal adapter to look up the user record,
|
|
553
|
+
* so it works with whichever database Better Auth is configured with.
|
|
554
|
+
*
|
|
555
|
+
* @param userId - User identifier
|
|
556
|
+
* @returns EE User object or null if not found
|
|
557
|
+
*/
|
|
558
|
+
async getUser(userId) {
|
|
559
|
+
try {
|
|
560
|
+
const ctx = await this.getAuthContext();
|
|
561
|
+
if (!ctx) return null;
|
|
562
|
+
const user = await ctx.internalAdapter.findUserById(userId);
|
|
563
|
+
if (!user) return null;
|
|
564
|
+
return mapBetterAuthUserToEEUser(user);
|
|
565
|
+
} catch {
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Get multiple users by ID.
|
|
571
|
+
* Optional IUserProvider method used for batch author enrichment.
|
|
572
|
+
*
|
|
573
|
+
* @param userIds - User identifiers
|
|
574
|
+
* @returns EE User objects (null for missing users, order preserved)
|
|
575
|
+
*/
|
|
576
|
+
async getUsers(userIds) {
|
|
577
|
+
return Promise.all(userIds.map((userId) => this.getUser(userId)));
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Get URL to user's profile page.
|
|
581
|
+
* Optional IUserProvider method.
|
|
582
|
+
*/
|
|
583
|
+
getUserProfileUrl(user) {
|
|
584
|
+
return `/profile/${user.id}`;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Get the resolved Better Auth context, or null when unavailable
|
|
588
|
+
* (e.g. a partial mock without `$context`).
|
|
589
|
+
*/
|
|
590
|
+
async getAuthContext() {
|
|
591
|
+
try {
|
|
592
|
+
return await Promise.resolve(this.auth.$context) ?? null;
|
|
593
|
+
} catch {
|
|
594
|
+
return null;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Ensure the Cookie header on `headers` carries a Better Auth session cookie
|
|
599
|
+
* for the given token.
|
|
600
|
+
*
|
|
601
|
+
* Better Auth's `getSession` reads the session from a *signed* cookie
|
|
602
|
+
* (`<token>.<HMAC-SHA256 signature>`), while `signIn`/`signUp` return the
|
|
603
|
+
* raw unsigned token. Mirroring Better Auth's bearer plugin, unsigned tokens
|
|
604
|
+
* are signed with the instance secret before being set as the session cookie.
|
|
605
|
+
*/
|
|
606
|
+
async ensureSessionCookie(headers, token) {
|
|
607
|
+
if (!token) return;
|
|
608
|
+
const ctx = await this.getAuthContext();
|
|
609
|
+
const cookieName = ctx?.authCookies.sessionToken.name ?? this.sessionCookieName;
|
|
610
|
+
const secret = ctx?.secret;
|
|
611
|
+
const cookieHeader = headers.get("Cookie");
|
|
612
|
+
if (!!cookieHeader?.split(";").some((pair) => {
|
|
613
|
+
const [key] = pair.trim().split("=");
|
|
614
|
+
return key?.trim() === cookieName;
|
|
615
|
+
})) return;
|
|
616
|
+
let cookieValue = token.includes(".") ? token.includes("%") ? tryDecode(token) : token : token;
|
|
617
|
+
if (!token.includes(".") && secret) cookieValue = `${token}.${await makeSignature(token, secret)}`;
|
|
618
|
+
const existingCookies = cookieHeader ? `${cookieHeader}; ` : "";
|
|
619
|
+
headers.set("Cookie", `${existingCookies}${cookieName}=${encodeURIComponent(cookieValue)}`);
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Authenticate a bearer token by verifying the session with Better Auth.
|
|
623
|
+
*
|
|
624
|
+
* This method extracts the session from the request headers using
|
|
625
|
+
* Better Auth's `api.getSession()` endpoint. Raw (unsigned) session tokens —
|
|
626
|
+
* as returned by `signIn`/`signUp` — are signed before being passed to
|
|
627
|
+
* Better Auth as a session cookie, matching the bearer plugin's semantics.
|
|
628
|
+
*
|
|
629
|
+
* @param token - The bearer token (session token) to authenticate
|
|
630
|
+
* @param request - The request containing headers
|
|
631
|
+
* @returns The authenticated user and session, or null if authentication fails
|
|
632
|
+
*/
|
|
633
|
+
async authenticateToken(token, request) {
|
|
634
|
+
try {
|
|
635
|
+
await this.#ensureDbReady();
|
|
636
|
+
const headers = new Headers();
|
|
637
|
+
const cookieHeader = getRequestHeader(request, "Cookie");
|
|
638
|
+
if (cookieHeader) headers.set("Cookie", cookieHeader);
|
|
639
|
+
await this.ensureSessionCookie(headers, token);
|
|
640
|
+
const result = await this.auth.api.getSession({ headers });
|
|
641
|
+
if (!result || !result.session || !result.user) return null;
|
|
642
|
+
return {
|
|
643
|
+
session: result.session,
|
|
644
|
+
user: result.user
|
|
645
|
+
};
|
|
646
|
+
} catch {
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Authorize a user for access.
|
|
652
|
+
*
|
|
653
|
+
* By default, any authenticated user with a valid session is authorized.
|
|
654
|
+
* You can override this behavior by providing a custom `authorizeUser` function
|
|
655
|
+
* in the constructor options.
|
|
656
|
+
*
|
|
657
|
+
* @param user - The authenticated user and session
|
|
658
|
+
* @returns True if the user is authorized, false otherwise
|
|
659
|
+
*/
|
|
660
|
+
async authorizeUser(user) {
|
|
661
|
+
return !!user?.session?.id && !!user?.user?.id;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Sign in with email and password.
|
|
665
|
+
* Implements ICredentialsProvider for EE credentials auth.
|
|
666
|
+
*
|
|
667
|
+
* @param email - User email
|
|
668
|
+
* @param password - User password
|
|
669
|
+
* @param request - Incoming HTTP request
|
|
670
|
+
* @returns Result with user and session cookies
|
|
671
|
+
* @throws Error if credentials are invalid
|
|
672
|
+
*/
|
|
673
|
+
async signIn(email, password, request) {
|
|
674
|
+
const headers = request?.headers ?? new Headers();
|
|
675
|
+
const response = await this.auth.api.signInEmail({
|
|
676
|
+
body: {
|
|
677
|
+
email,
|
|
678
|
+
password
|
|
679
|
+
},
|
|
680
|
+
headers,
|
|
681
|
+
asResponse: true
|
|
682
|
+
});
|
|
683
|
+
if (!response.ok) {
|
|
684
|
+
const errorData = await response.json().catch(() => ({}));
|
|
685
|
+
throw new Error(errorData.message || "Invalid email or password");
|
|
686
|
+
}
|
|
687
|
+
const result = await response.json();
|
|
688
|
+
if (!result?.user) throw new Error("Invalid email or password");
|
|
689
|
+
const cookies = [];
|
|
690
|
+
const setCookieHeader = response.headers.get("set-cookie");
|
|
691
|
+
if (setCookieHeader) cookies.push(...setCookieHeader.split(/,(?=\s*\w+=)/));
|
|
692
|
+
return {
|
|
693
|
+
user: mapBetterAuthUserToEEUser(result.user),
|
|
694
|
+
token: result.token ?? void 0,
|
|
695
|
+
cookies
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Sign up with email and password.
|
|
700
|
+
* Implements ICredentialsProvider for EE credentials auth.
|
|
701
|
+
*
|
|
702
|
+
* @param email - User email
|
|
703
|
+
* @param password - User password
|
|
704
|
+
* @param name - Optional display name
|
|
705
|
+
* @param request - Incoming HTTP request
|
|
706
|
+
* @returns Result with new user and session cookies
|
|
707
|
+
* @throws Error if sign up fails
|
|
708
|
+
*/
|
|
709
|
+
async signUp(email, password, name, request) {
|
|
710
|
+
const displayName = name ?? email.split("@")[0] ?? "User";
|
|
711
|
+
const headers = request?.headers ?? new Headers();
|
|
712
|
+
const response = await this.auth.api.signUpEmail({
|
|
713
|
+
body: {
|
|
714
|
+
email,
|
|
715
|
+
password,
|
|
716
|
+
name: displayName
|
|
717
|
+
},
|
|
718
|
+
headers,
|
|
719
|
+
asResponse: true
|
|
720
|
+
});
|
|
721
|
+
if (!response.ok) {
|
|
722
|
+
const errorData = await response.json().catch(() => ({}));
|
|
723
|
+
throw new Error(errorData.message || "Failed to create account");
|
|
724
|
+
}
|
|
725
|
+
const result = await response.json();
|
|
726
|
+
if (!result?.user) throw new Error("Failed to create account");
|
|
727
|
+
const cookies = [];
|
|
728
|
+
const setCookieHeader = response.headers.get("set-cookie");
|
|
729
|
+
if (setCookieHeader) cookies.push(...setCookieHeader.split(/,(?=\s*\w+=)/));
|
|
730
|
+
return {
|
|
731
|
+
user: mapBetterAuthUserToEEUser(result.user),
|
|
732
|
+
token: result.token ?? void 0,
|
|
733
|
+
cookies
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Get the underlying Better Auth instance.
|
|
738
|
+
* Useful for accessing Better Auth APIs directly.
|
|
739
|
+
*/
|
|
740
|
+
getAuth() {
|
|
741
|
+
return this.auth;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Get headers to clear the session cookies on logout.
|
|
745
|
+
* Partial ISessionProvider implementation for logout support.
|
|
746
|
+
*
|
|
747
|
+
* Clears Better Auth's default session cookies.
|
|
748
|
+
*/
|
|
749
|
+
getClearSessionHeaders() {
|
|
750
|
+
const sameSite = this.#crossSite ? "None; Secure" : "Lax";
|
|
751
|
+
return { "Set-Cookie": [`${this.sessionCookieName}=; Path=/; HttpOnly; SameSite=${sameSite}; Max-Age=0`, `${this.sessionCookieName}_sig=; Path=/; HttpOnly; SameSite=${sameSite}; Max-Age=0`].join(", ") };
|
|
752
|
+
}
|
|
723
753
|
};
|
|
724
|
-
|
|
754
|
+
//#endregion
|
|
725
755
|
export { MastraAuthBetterAuth };
|
|
726
|
-
|
|
756
|
+
|
|
727
757
|
//# sourceMappingURL=index.js.map
|