@rebasepro/server-core 0.7.0 → 0.9.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 +2 -2
- package/dist/api/ast-schema-editor.d.ts +21 -0
- package/dist/api/collections_for_test/callbacks_test_collection.d.ts +2 -0
- package/dist/api/errors.d.ts +53 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/logs-routes.d.ts +37 -0
- package/dist/api/openapi-generator.d.ts +16 -0
- package/dist/api/rest/api-generator.d.ts +68 -0
- package/{src/api/rest/index.ts → dist/api/rest/index.d.ts} +0 -1
- package/dist/api/rest/query-parser.d.ts +6 -0
- package/dist/api/schema-editor-routes.d.ts +3 -0
- package/dist/api/types.d.ts +56 -0
- package/dist/auth/adapter-middleware.d.ts +43 -0
- package/dist/auth/admin-roles-route.d.ts +18 -0
- package/dist/auth/admin-user-ops.d.ts +79 -0
- package/dist/auth/admin-users-route.d.ts +28 -0
- package/dist/auth/api-keys/api-key-middleware.d.ts +39 -0
- package/{src/auth/api-keys/api-key-permission-guard.ts → dist/auth/api-keys/api-key-permission-guard.d.ts} +2 -34
- package/dist/auth/api-keys/api-key-routes.d.ts +20 -0
- package/dist/auth/api-keys/api-key-store.d.ts +35 -0
- package/{src/auth/api-keys/api-key-types.ts → dist/auth/api-keys/api-key-types.d.ts} +0 -6
- package/{src/auth/api-keys/index.ts → dist/auth/api-keys/index.d.ts} +2 -22
- package/dist/auth/apple-oauth.d.ts +30 -0
- package/{src/auth/auth-hooks.ts → dist/auth/auth-hooks.d.ts} +15 -73
- package/dist/auth/bitbucket-oauth.d.ts +11 -0
- package/dist/auth/builtin-auth-adapter.d.ts +55 -0
- package/dist/auth/cookie-utils.d.ts +32 -0
- package/{src/auth/crypto-utils.ts → dist/auth/crypto-utils.d.ts} +1 -16
- package/dist/auth/custom-auth-adapter.d.ts +39 -0
- package/dist/auth/discord-oauth.d.ts +14 -0
- package/dist/auth/facebook-oauth.d.ts +14 -0
- package/dist/auth/github-oauth.d.ts +15 -0
- package/dist/auth/gitlab-oauth.d.ts +13 -0
- package/dist/auth/google-oauth.d.ts +47 -0
- package/{src/auth/index.ts → dist/auth/index.d.ts} +4 -22
- package/{src/auth/interfaces.ts → dist/auth/interfaces.d.ts} +20 -89
- package/dist/auth/jwt.d.ts +67 -0
- package/dist/auth/linkedin-oauth.d.ts +18 -0
- package/dist/auth/magic-link-routes.d.ts +32 -0
- package/dist/auth/mfa-crypto.d.ts +23 -0
- package/dist/auth/mfa-routes.d.ts +7 -0
- package/dist/auth/mfa.d.ts +49 -0
- package/dist/auth/microsoft-oauth.d.ts +16 -0
- package/dist/auth/middleware.d.ts +158 -0
- package/dist/auth/password.d.ts +22 -0
- package/dist/auth/rate-limiter.d.ts +50 -0
- package/dist/auth/reset-password-admin.d.ts +29 -0
- package/dist/auth/rls-scope.d.ts +31 -0
- package/dist/auth/routes.d.ts +66 -0
- package/dist/auth/session-routes.d.ts +29 -0
- package/dist/auth/slack-oauth.d.ts +12 -0
- package/dist/auth/spotify-oauth.d.ts +12 -0
- package/dist/auth/twitter-oauth.d.ts +18 -0
- package/dist/collections/BackendCollectionRegistry.d.ts +13 -0
- package/dist/collections/loader.d.ts +5 -0
- package/dist/cron/cron-loader.d.ts +18 -0
- package/dist/cron/cron-routes.d.ts +14 -0
- package/dist/cron/cron-scheduler.d.ts +106 -0
- package/dist/cron/cron-store.d.ts +32 -0
- package/dist/cron/define-cron.d.ts +28 -0
- package/{src/cron/index.ts → dist/cron/index.d.ts} +1 -0
- package/dist/db/interfaces.d.ts +18 -0
- package/dist/dynamic-import-Dvh-K5fl.js +22 -0
- package/dist/dynamic-import-Dvh-K5fl.js.map +1 -0
- package/dist/email/index.d.ts +6 -0
- package/dist/email/smtp-email-service.d.ts +30 -0
- package/dist/email/templates.d.ts +50 -0
- package/{src/email/types.ts → dist/email/types.d.ts} +40 -36
- package/dist/env.d.ts +102 -0
- package/dist/functions/define-function.d.ts +55 -0
- package/dist/functions/function-loader.d.ts +18 -0
- package/dist/functions/function-routes.d.ts +10 -0
- package/{src/functions/index.ts → dist/functions/index.d.ts} +2 -0
- package/dist/history/history-routes.d.ts +23 -0
- package/{src/history/index.ts → dist/history/index.d.ts} +0 -1
- package/dist/index.d.ts +35 -0
- package/dist/index.es.js +18184 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +48629 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/init/docs.d.ts +4 -0
- package/dist/init/health.d.ts +2 -0
- package/dist/init/middlewares.d.ts +10 -0
- package/dist/init/shutdown.d.ts +65 -0
- package/dist/init/storage.d.ts +5 -0
- package/dist/init.d.ts +344 -0
- package/dist/jwt-BwIn8xmk.js +4200 -0
- package/dist/jwt-BwIn8xmk.js.map +1 -0
- package/dist/logger-BYU66ENZ.js +94 -0
- package/dist/logger-BYU66ENZ.js.map +1 -0
- package/dist/ms-DnYXB-Wd.js +162 -0
- package/dist/ms-DnYXB-Wd.js.map +1 -0
- package/dist/openapi-generator-Z9oYWLf_.js +586 -0
- package/dist/openapi-generator-Z9oYWLf_.js.map +1 -0
- package/dist/schema-editor-routes-D3ef8zu1.js +219 -0
- package/dist/schema-editor-routes-D3ef8zu1.js.map +1 -0
- package/dist/serve-spa.d.ts +36 -0
- package/dist/services/driver-registry.d.ts +78 -0
- package/dist/services/routed-realtime-service.d.ts +43 -0
- package/dist/services/webhook-service.d.ts +29 -0
- package/dist/singleton.d.ts +51 -0
- package/dist/src-B4OLmNVa.js +437 -0
- package/dist/src-B4OLmNVa.js.map +1 -0
- package/dist/src-B5WkP1Rv.js +24778 -0
- package/dist/src-B5WkP1Rv.js.map +1 -0
- package/dist/storage/GCSStorageController.d.ts +43 -0
- package/dist/storage/LocalStorageController.d.ts +46 -0
- package/dist/storage/S3StorageController.d.ts +40 -0
- package/dist/storage/image-transform.d.ts +50 -0
- package/{src/storage/index.ts → dist/storage/index.d.ts} +6 -21
- package/dist/storage/routes.d.ts +70 -0
- package/dist/storage/storage-registry.d.ts +78 -0
- package/dist/storage/tus-handler.d.ts +53 -0
- package/{src/storage/types.ts → dist/storage/types.d.ts} +32 -38
- package/dist/types/index.d.ts +11 -0
- package/dist/utils/dev-port.d.ts +45 -0
- package/dist/utils/dynamic-import.d.ts +25 -0
- package/dist/utils/logger.d.ts +31 -0
- package/dist/utils/logging.d.ts +9 -0
- package/dist/utils/request-id.d.ts +4 -0
- package/dist/utils/request-logger.d.ts +19 -0
- package/{src/utils/sql.ts → dist/utils/sql.d.ts} +5 -16
- package/package.json +49 -22
- package/coverage/clover.xml +0 -3739
- package/coverage/coverage-final.json +0 -31
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -266
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov-report/src/api/ast-schema-editor.ts.html +0 -952
- package/coverage/lcov-report/src/api/errors.ts.html +0 -472
- package/coverage/lcov-report/src/api/graphql/graphql-schema-generator.ts.html +0 -1069
- package/coverage/lcov-report/src/api/graphql/index.html +0 -116
- package/coverage/lcov-report/src/api/index.html +0 -176
- package/coverage/lcov-report/src/api/openapi-generator.ts.html +0 -565
- package/coverage/lcov-report/src/api/rest/api-generator.ts.html +0 -994
- package/coverage/lcov-report/src/api/rest/index.html +0 -131
- package/coverage/lcov-report/src/api/rest/query-parser.ts.html +0 -550
- package/coverage/lcov-report/src/api/schema-editor-routes.ts.html +0 -202
- package/coverage/lcov-report/src/api/server.ts.html +0 -823
- package/coverage/lcov-report/src/auth/admin-routes.ts.html +0 -973
- package/coverage/lcov-report/src/auth/index.html +0 -176
- package/coverage/lcov-report/src/auth/jwt.ts.html +0 -574
- package/coverage/lcov-report/src/auth/middleware.ts.html +0 -745
- package/coverage/lcov-report/src/auth/password.ts.html +0 -310
- package/coverage/lcov-report/src/auth/services.ts.html +0 -2074
- package/coverage/lcov-report/src/collections/index.html +0 -116
- package/coverage/lcov-report/src/collections/loader.ts.html +0 -232
- package/coverage/lcov-report/src/db/auth-schema.ts.html +0 -523
- package/coverage/lcov-report/src/db/data-transformer.ts.html +0 -1753
- package/coverage/lcov-report/src/db/entityService.ts.html +0 -700
- package/coverage/lcov-report/src/db/index.html +0 -146
- package/coverage/lcov-report/src/db/services/EntityFetchService.ts.html +0 -4048
- package/coverage/lcov-report/src/db/services/EntityPersistService.ts.html +0 -883
- package/coverage/lcov-report/src/db/services/RelationService.ts.html +0 -3121
- package/coverage/lcov-report/src/db/services/entity-helpers.ts.html +0 -442
- package/coverage/lcov-report/src/db/services/index.html +0 -176
- package/coverage/lcov-report/src/db/services/index.ts.html +0 -124
- package/coverage/lcov-report/src/generate-drizzle-schema-logic.ts.html +0 -1960
- package/coverage/lcov-report/src/index.html +0 -116
- package/coverage/lcov-report/src/services/driver-registry.ts.html +0 -631
- package/coverage/lcov-report/src/services/index.html +0 -131
- package/coverage/lcov-report/src/services/postgresDataDriver.ts.html +0 -3025
- package/coverage/lcov-report/src/storage/LocalStorageController.ts.html +0 -1189
- package/coverage/lcov-report/src/storage/S3StorageController.ts.html +0 -970
- package/coverage/lcov-report/src/storage/index.html +0 -161
- package/coverage/lcov-report/src/storage/storage-registry.ts.html +0 -646
- package/coverage/lcov-report/src/storage/types.ts.html +0 -451
- package/coverage/lcov-report/src/utils/drizzle-conditions.ts.html +0 -3082
- package/coverage/lcov-report/src/utils/index.html +0 -116
- package/coverage/lcov.info +0 -7179
- package/jest.config.cjs +0 -19
- package/src/api/ast-schema-editor.ts +0 -289
- package/src/api/collections_for_test/callbacks_test_collection.ts +0 -61
- package/src/api/errors.ts +0 -278
- package/src/api/graphql/graphql-schema-generator.ts +0 -383
- package/src/api/graphql/index.ts +0 -2
- package/src/api/index.ts +0 -11
- package/src/api/logs-routes.ts +0 -132
- package/src/api/openapi-generator.ts +0 -727
- package/src/api/rest/api-generator-count.test.ts +0 -126
- package/src/api/rest/api-generator.ts +0 -747
- package/src/api/rest/query-parser.ts +0 -300
- package/src/api/schema-editor-routes.ts +0 -41
- package/src/api/server.ts +0 -271
- package/src/api/types.ts +0 -104
- package/src/auth/adapter-middleware.ts +0 -114
- package/src/auth/admin-roles-route.ts +0 -36
- package/src/auth/admin-user-ops.ts +0 -236
- package/src/auth/admin-users-route.ts +0 -302
- package/src/auth/api-keys/api-key-middleware.ts +0 -133
- package/src/auth/api-keys/api-key-routes.ts +0 -193
- package/src/auth/api-keys/api-key-store.ts +0 -340
- package/src/auth/apple-oauth.ts +0 -122
- package/src/auth/bitbucket-oauth.ts +0 -84
- package/src/auth/builtin-auth-adapter.ts +0 -380
- package/src/auth/custom-auth-adapter.ts +0 -86
- package/src/auth/discord-oauth.ts +0 -85
- package/src/auth/facebook-oauth.ts +0 -74
- package/src/auth/github-oauth.ts +0 -112
- package/src/auth/gitlab-oauth.ts +0 -72
- package/src/auth/google-oauth.ts +0 -223
- package/src/auth/jwt.ts +0 -208
- package/src/auth/linkedin-oauth.ts +0 -83
- package/src/auth/magic-link-routes.ts +0 -167
- package/src/auth/mfa-crypto.ts +0 -91
- package/src/auth/mfa-routes.ts +0 -323
- package/src/auth/mfa.ts +0 -161
- package/src/auth/microsoft-oauth.ts +0 -90
- package/src/auth/middleware.ts +0 -369
- package/src/auth/password.ts +0 -77
- package/src/auth/rate-limiter.ts +0 -225
- package/src/auth/reset-password-admin.ts +0 -160
- package/src/auth/rls-scope.ts +0 -58
- package/src/auth/routes.ts +0 -802
- package/src/auth/session-routes.ts +0 -353
- package/src/auth/slack-oauth.ts +0 -73
- package/src/auth/spotify-oauth.ts +0 -69
- package/src/auth/twitter-oauth.ts +0 -128
- package/src/collections/BackendCollectionRegistry.ts +0 -20
- package/src/collections/loader.ts +0 -50
- package/src/cron/cron-loader.ts +0 -84
- package/src/cron/cron-routes.test.ts +0 -265
- package/src/cron/cron-routes.ts +0 -85
- package/src/cron/cron-scheduler.test.ts +0 -559
- package/src/cron/cron-scheduler.ts +0 -581
- package/src/cron/cron-store.ts +0 -166
- package/src/db/interfaces.ts +0 -60
- package/src/email/index.ts +0 -19
- package/src/email/smtp-email-service.ts +0 -123
- package/src/email/templates.ts +0 -470
- package/src/env.ts +0 -227
- package/src/functions/function-loader.ts +0 -116
- package/src/functions/function-routes.ts +0 -31
- package/src/history/history-routes.ts +0 -129
- package/src/index.ts +0 -70
- package/src/init/docs.ts +0 -47
- package/src/init/health.ts +0 -37
- package/src/init/middlewares.ts +0 -61
- package/src/init/shutdown.ts +0 -56
- package/src/init/storage.ts +0 -57
- package/src/init.ts +0 -1041
- package/src/serve-spa.ts +0 -89
- package/src/services/driver-registry.ts +0 -183
- package/src/services/routed-realtime-service.ts +0 -113
- package/src/services/webhook-service.ts +0 -155
- package/src/singleton.ts +0 -70
- package/src/storage/LocalStorageController.ts +0 -389
- package/src/storage/S3StorageController.ts +0 -300
- package/src/storage/image-transform.ts +0 -218
- package/src/storage/routes.ts +0 -368
- package/src/storage/storage-registry.ts +0 -188
- package/src/storage/tus-handler.ts +0 -315
- package/src/types/index.ts +0 -27
- package/src/utils/dev-port.ts +0 -196
- package/src/utils/logger.ts +0 -143
- package/src/utils/logging.ts +0 -40
- package/src/utils/request-id.ts +0 -40
- package/src/utils/request-logger.ts +0 -72
- package/test/api-generator.test.ts +0 -588
- package/test/api-key-permission-guard.test.ts +0 -144
- package/test/ast-schema-editor.test.ts +0 -89
- package/test/auth-middleware-hono.test.ts +0 -556
- package/test/auth-routes.test.ts +0 -1098
- package/test/backend-hooks-data.test.ts +0 -477
- package/test/custom-auth-adapter.test.ts +0 -189
- package/test/driver-registry.test.ts +0 -282
- package/test/email-templates.test.ts +0 -174
- package/test/env.test.ts +0 -128
- package/test/error-propagation.test.ts +0 -226
- package/test/errors-hono.test.ts +0 -133
- package/test/errors.test.ts +0 -155
- package/test/function-loader.test.ts +0 -127
- package/test/graphql-schema-generator.test.ts +0 -554
- package/test/jwt-security.test.ts +0 -182
- package/test/jwt.test.ts +0 -326
- package/test/mfa.test.ts +0 -197
- package/test/middleware.test.ts +0 -305
- package/test/multi-datasource-routing.test.ts +0 -113
- package/test/password.test.ts +0 -165
- package/test/query-parser.test.ts +0 -233
- package/test/rate-limiter.test.ts +0 -102
- package/test/reset-password-admin.test.ts +0 -113
- package/test/routed-realtime-service.test.ts +0 -86
- package/test/safe-compare.test.ts +0 -66
- package/test/singleton.test.ts +0 -59
- package/test/smtp-email-service.test.ts +0 -169
- package/test/storage-local.test.ts +0 -271
- package/test/storage-registry.test.ts +0 -282
- package/test/storage-routes.test.ts +0 -222
- package/test/storage-s3.test.ts +0 -304
- package/test/transform-auth-response.test.ts +0 -305
- package/test/webhook-service.test.ts +0 -260
- package/tsconfig.json +0 -54
- package/tsconfig.prod.json +0 -23
- package/vite.config.ts +0 -86
|
@@ -1,380 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RebaseBuiltinAuthAdapter
|
|
3
|
-
*
|
|
4
|
-
* Wraps Rebase's existing built-in JWT auth system (routes, middleware, user/role
|
|
5
|
-
* management) into the `AuthAdapter` interface. This is the default adapter used
|
|
6
|
-
* when the user passes a plain `RebaseAuthConfig` object.
|
|
7
|
-
*
|
|
8
|
-
* This is NOT a rewrite — it delegates to the existing `createAuthRoutes()`,
|
|
9
|
-
* `createResetPasswordRoute()`, and `verifyAccessToken()` functions. The goal is to
|
|
10
|
-
* present the same functionality through the pluggable `AuthAdapter` contract.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type {
|
|
14
|
-
AuthAdapter,
|
|
15
|
-
AuthenticatedUser,
|
|
16
|
-
AuthAdapterCapabilities,
|
|
17
|
-
UserManagementAdapter,
|
|
18
|
-
AuthUserListOptions,
|
|
19
|
-
AuthUserListResult,
|
|
20
|
-
AuthUserData,
|
|
21
|
-
AuthCreateUserData,
|
|
22
|
-
BootstrappedAuth
|
|
23
|
-
} from "@rebasepro/types";
|
|
24
|
-
|
|
25
|
-
import { Hono } from "hono";
|
|
26
|
-
import { verifyAccessToken } from "./jwt";
|
|
27
|
-
import type { AccessTokenPayload } from "./jwt";
|
|
28
|
-
import { createAuthRoutes } from "./routes";
|
|
29
|
-
import { createResetPasswordRoute } from "./reset-password-admin";
|
|
30
|
-
import { createAdminRolesRoute } from "./admin-roles-route";
|
|
31
|
-
import { createAdminUsersRoute } from "./admin-users-route";
|
|
32
|
-
import { prepareAdminUserValues, finalizeAdminUserCreation } from "./admin-user-ops";
|
|
33
|
-
import type { AuthRepository, OAuthProvider } from "./interfaces";
|
|
34
|
-
import type { AuthHooks, ResolvedAuthHooks } from "./auth-hooks";
|
|
35
|
-
import { resolveAuthHooks } from "./auth-hooks";
|
|
36
|
-
import type { EmailService, EmailConfig } from "../email";
|
|
37
|
-
import type { HonoEnv } from "../api/types";
|
|
38
|
-
import { safeCompare } from "./crypto-utils";
|
|
39
|
-
import { logger } from "../utils/logger";
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Configuration for the built-in Rebase auth adapter.
|
|
43
|
-
*
|
|
44
|
-
* This mirrors the existing `RebaseAuthConfig` — users pass this and
|
|
45
|
-
* server-core auto-wraps it in a `RebaseBuiltinAuthAdapter`.
|
|
46
|
-
*/
|
|
47
|
-
export interface BuiltinAuthAdapterConfig {
|
|
48
|
-
/** The bootstrapper-provided auth repository (users, roles, tokens). */
|
|
49
|
-
authRepository: AuthRepository;
|
|
50
|
-
/** Email service for password resets, verification, etc. */
|
|
51
|
-
emailService?: EmailService;
|
|
52
|
-
/** Email configuration. */
|
|
53
|
-
emailConfig?: EmailConfig;
|
|
54
|
-
/** Whether to allow new user registration. */
|
|
55
|
-
allowRegistration?: boolean;
|
|
56
|
-
/** Default role to assign to new users. */
|
|
57
|
-
defaultRole?: string;
|
|
58
|
-
/** OAuth providers to register. */
|
|
59
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
|
-
oauthProviders?: OAuthProvider<any>[];
|
|
61
|
-
/** Static service key for server-to-server auth. */
|
|
62
|
-
serviceKey?: string;
|
|
63
|
-
/** Auth hooks for customizing password, credentials, lifecycle, etc. */
|
|
64
|
-
authHooks?: AuthHooks;
|
|
65
|
-
/** The parsed auth config from the collection (if `auth` is an object, not just `true`). */
|
|
66
|
-
collectionAuthConfig?: import("@rebasepro/types").AuthCollectionConfig;
|
|
67
|
-
/** Enable magic link (passwordless email) login. Requires email service. */
|
|
68
|
-
enableMagicLink?: boolean;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Create the built-in Rebase auth adapter.
|
|
73
|
-
*
|
|
74
|
-
* This wraps the existing auth infrastructure (JWT, OAuth, user/role management)
|
|
75
|
-
* into the `AuthAdapter` interface. It's used internally by `initializeRebaseBackend()`
|
|
76
|
-
* when the user passes a plain `RebaseAuthConfig` object.
|
|
77
|
-
*/
|
|
78
|
-
export function createBuiltinAuthAdapter(config: BuiltinAuthAdapterConfig): AuthAdapter {
|
|
79
|
-
const {
|
|
80
|
-
authRepository,
|
|
81
|
-
emailService,
|
|
82
|
-
emailConfig,
|
|
83
|
-
allowRegistration = false,
|
|
84
|
-
defaultRole,
|
|
85
|
-
oauthProviders = [],
|
|
86
|
-
serviceKey,
|
|
87
|
-
authHooks,
|
|
88
|
-
collectionAuthConfig,
|
|
89
|
-
enableMagicLink = false
|
|
90
|
-
} = config;
|
|
91
|
-
|
|
92
|
-
const resolvedOps = resolveAuthHooks(authHooks);
|
|
93
|
-
|
|
94
|
-
const adapter: AuthAdapter = {
|
|
95
|
-
id: "rebase-builtin",
|
|
96
|
-
|
|
97
|
-
serviceKey,
|
|
98
|
-
|
|
99
|
-
async verifyRequest(request: Request): Promise<AuthenticatedUser | null> {
|
|
100
|
-
const authHeader = request.headers.get("authorization");
|
|
101
|
-
const url = new URL(request.url, "http://localhost");
|
|
102
|
-
const queryToken = url.searchParams.get("token");
|
|
103
|
-
const hasBearer = authHeader?.startsWith("Bearer ");
|
|
104
|
-
|
|
105
|
-
if (!hasBearer && !queryToken) {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const token = hasBearer ? authHeader!.substring(7) : queryToken!;
|
|
110
|
-
|
|
111
|
-
// Check service key first (constant-time)
|
|
112
|
-
if (serviceKey && safeCompare(token, serviceKey)) {
|
|
113
|
-
return {
|
|
114
|
-
uid: "service",
|
|
115
|
-
email: "service@rebase.internal",
|
|
116
|
-
roles: ["admin"],
|
|
117
|
-
isAdmin: true,
|
|
118
|
-
rawToken: token
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// JWT verification
|
|
123
|
-
const payload = verifyAccessToken(token);
|
|
124
|
-
if (!payload) {
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Resolve roles from the repository
|
|
129
|
-
let roles: string[] = payload.roles || [];
|
|
130
|
-
try {
|
|
131
|
-
roles = await authRepository.getUserRoleIds(payload.userId);
|
|
132
|
-
} catch (err: unknown) {
|
|
133
|
-
logger.warn("Role lookup from repository failed, using token roles as fallback", { userId: payload.userId, error: err });
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const isAdmin = roles.some((r) => r === "admin" || r === "schema-admin");
|
|
137
|
-
|
|
138
|
-
return {
|
|
139
|
-
uid: payload.userId,
|
|
140
|
-
email: payload.email ?? "",
|
|
141
|
-
displayName: payload.displayName ?? null,
|
|
142
|
-
roles,
|
|
143
|
-
isAdmin,
|
|
144
|
-
rawToken: token
|
|
145
|
-
};
|
|
146
|
-
},
|
|
147
|
-
|
|
148
|
-
async verifyToken(token: string): Promise<AuthenticatedUser | null> {
|
|
149
|
-
// Service key check (constant-time)
|
|
150
|
-
if (serviceKey && safeCompare(token, serviceKey)) {
|
|
151
|
-
return {
|
|
152
|
-
uid: "service",
|
|
153
|
-
email: "service@rebase.internal",
|
|
154
|
-
roles: ["admin"],
|
|
155
|
-
isAdmin: true,
|
|
156
|
-
rawToken: token
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// JWT verification
|
|
161
|
-
const payload = verifyAccessToken(token);
|
|
162
|
-
if (!payload) {
|
|
163
|
-
return null;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
let roles: string[] = payload.roles || [];
|
|
167
|
-
try {
|
|
168
|
-
roles = await authRepository.getUserRoleIds(payload.userId);
|
|
169
|
-
} catch (err: unknown) {
|
|
170
|
-
logger.warn("Role lookup from repository failed, using token roles as fallback", { userId: payload.userId, error: err });
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const isAdmin = roles.some((r) => r === "admin" || r === "schema-admin");
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
uid: payload.userId,
|
|
177
|
-
email: payload.email ?? "",
|
|
178
|
-
displayName: payload.displayName ?? null,
|
|
179
|
-
roles,
|
|
180
|
-
isAdmin,
|
|
181
|
-
rawToken: token
|
|
182
|
-
};
|
|
183
|
-
},
|
|
184
|
-
|
|
185
|
-
userManagement: createUserManagementFromRepo(authRepository, resolvedOps),
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
createAuthRoutes(): Hono<HonoEnv> | undefined {
|
|
189
|
-
return createAuthRoutes({
|
|
190
|
-
authRepo: authRepository,
|
|
191
|
-
emailService,
|
|
192
|
-
emailConfig,
|
|
193
|
-
allowRegistration,
|
|
194
|
-
defaultRole,
|
|
195
|
-
oauthProviders,
|
|
196
|
-
authHooks,
|
|
197
|
-
enableMagicLink
|
|
198
|
-
});
|
|
199
|
-
},
|
|
200
|
-
|
|
201
|
-
createAdminRoutes(): Hono<HonoEnv> | undefined {
|
|
202
|
-
const router = new Hono<HonoEnv>();
|
|
203
|
-
const resetPasswordRoute = createResetPasswordRoute({
|
|
204
|
-
authRepo: authRepository,
|
|
205
|
-
emailService,
|
|
206
|
-
emailConfig,
|
|
207
|
-
serviceKey,
|
|
208
|
-
authHooks,
|
|
209
|
-
collectionAuthConfig
|
|
210
|
-
});
|
|
211
|
-
const rolesRoute = createAdminRolesRoute({
|
|
212
|
-
authRepo: authRepository,
|
|
213
|
-
serviceKey
|
|
214
|
-
});
|
|
215
|
-
const adminUsersRoute = createAdminUsersRoute({
|
|
216
|
-
authRepo: authRepository,
|
|
217
|
-
emailService,
|
|
218
|
-
emailConfig,
|
|
219
|
-
serviceKey,
|
|
220
|
-
authHooks,
|
|
221
|
-
collectionAuthConfig
|
|
222
|
-
});
|
|
223
|
-
router.route("/", resetPasswordRoute);
|
|
224
|
-
router.route("/", rolesRoute);
|
|
225
|
-
router.route("/", adminUsersRoute);
|
|
226
|
-
return router;
|
|
227
|
-
},
|
|
228
|
-
|
|
229
|
-
async prepareUserCreation(values, collectionAuth) {
|
|
230
|
-
const parsedCollectionAuth = collectionAuth as import("@rebasepro/types").AuthCollectionConfig | undefined;
|
|
231
|
-
return prepareAdminUserValues(values, {
|
|
232
|
-
authRepo: authRepository,
|
|
233
|
-
emailService,
|
|
234
|
-
emailConfig,
|
|
235
|
-
resolvedHooks: resolvedOps,
|
|
236
|
-
collectionAuthConfig: parsedCollectionAuth ?? collectionAuthConfig
|
|
237
|
-
});
|
|
238
|
-
},
|
|
239
|
-
|
|
240
|
-
async finalizeUserCreation(entity, clearPassword) {
|
|
241
|
-
return finalizeAdminUserCreation(entity, clearPassword, {
|
|
242
|
-
authRepo: authRepository,
|
|
243
|
-
emailService,
|
|
244
|
-
emailConfig,
|
|
245
|
-
resolvedHooks: resolvedOps,
|
|
246
|
-
collectionAuthConfig
|
|
247
|
-
});
|
|
248
|
-
},
|
|
249
|
-
|
|
250
|
-
async getCapabilities(): Promise<AuthAdapterCapabilities> {
|
|
251
|
-
// Detect bootstrap mode: are there any users?
|
|
252
|
-
let needsSetup = false;
|
|
253
|
-
try {
|
|
254
|
-
const result = await authRepository.listUsersPaginated({ limit: 1 });
|
|
255
|
-
needsSetup = result.total === 0;
|
|
256
|
-
} catch {
|
|
257
|
-
// If the check fails, assume not in setup mode
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const enabledProviders = oauthProviders.map((p) => p.id);
|
|
261
|
-
|
|
262
|
-
return {
|
|
263
|
-
hasBuiltInAuthRoutes: true,
|
|
264
|
-
emailPasswordLogin: true,
|
|
265
|
-
registration: allowRegistration || needsSetup,
|
|
266
|
-
registrationEnabled: allowRegistration || needsSetup,
|
|
267
|
-
passwordReset: !!emailService?.isConfigured(),
|
|
268
|
-
sessionManagement: true,
|
|
269
|
-
profileUpdate: true,
|
|
270
|
-
emailVerification: !!emailService?.isConfigured(),
|
|
271
|
-
magicLink: enableMagicLink && !!emailService?.isConfigured(),
|
|
272
|
-
enabledProviders,
|
|
273
|
-
needsSetup
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
return adapter;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// ─── Internal Helpers ────────────────────────────────────────────────────────
|
|
282
|
-
|
|
283
|
-
function createUserManagementFromRepo(repo: AuthRepository, resolvedOps: ResolvedAuthHooks): UserManagementAdapter {
|
|
284
|
-
return {
|
|
285
|
-
async listUsers(options?: AuthUserListOptions): Promise<AuthUserListResult> {
|
|
286
|
-
const result = await repo.listUsersPaginated({
|
|
287
|
-
limit: options?.limit,
|
|
288
|
-
offset: options?.offset,
|
|
289
|
-
search: options?.search,
|
|
290
|
-
orderBy: options?.orderBy,
|
|
291
|
-
orderDir: options?.orderDir,
|
|
292
|
-
roleId: options?.roleId
|
|
293
|
-
});
|
|
294
|
-
return {
|
|
295
|
-
users: result.users.map(toAuthUserData),
|
|
296
|
-
total: result.total,
|
|
297
|
-
limit: result.limit,
|
|
298
|
-
offset: result.offset
|
|
299
|
-
};
|
|
300
|
-
},
|
|
301
|
-
|
|
302
|
-
async getUserById(id: string): Promise<AuthUserData | null> {
|
|
303
|
-
const user = await repo.getUserById(id);
|
|
304
|
-
return user ? toAuthUserData(user) : null;
|
|
305
|
-
},
|
|
306
|
-
|
|
307
|
-
async createUser(data: AuthCreateUserData): Promise<AuthUserData> {
|
|
308
|
-
const passwordHash = data.password ? await resolvedOps.hashPassword(data.password) : undefined;
|
|
309
|
-
let createData: import("./interfaces").CreateUserData = {
|
|
310
|
-
email: data.email,
|
|
311
|
-
passwordHash,
|
|
312
|
-
displayName: data.displayName,
|
|
313
|
-
photoUrl: data.photoUrl,
|
|
314
|
-
metadata: data.metadata
|
|
315
|
-
};
|
|
316
|
-
if (resolvedOps.beforeUserCreate) {
|
|
317
|
-
createData = await resolvedOps.beforeUserCreate(createData);
|
|
318
|
-
}
|
|
319
|
-
const user = await repo.createUser(createData);
|
|
320
|
-
if (resolvedOps.afterUserCreate) {
|
|
321
|
-
try {
|
|
322
|
-
await resolvedOps.afterUserCreate(user);
|
|
323
|
-
} catch (err) {
|
|
324
|
-
logger.error("[AuthHooks] afterUserCreate error", { error: err instanceof Error ? err.message : err });
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
return toAuthUserData(user);
|
|
328
|
-
},
|
|
329
|
-
|
|
330
|
-
async updateUser(id: string, data: Partial<AuthCreateUserData>): Promise<AuthUserData | null> {
|
|
331
|
-
const updateData: Record<string, unknown> = {};
|
|
332
|
-
if (data.email !== undefined) updateData.email = data.email;
|
|
333
|
-
if (data.displayName !== undefined) updateData.displayName = data.displayName;
|
|
334
|
-
if (data.photoUrl !== undefined) updateData.photoUrl = data.photoUrl;
|
|
335
|
-
if (data.metadata !== undefined) updateData.metadata = data.metadata;
|
|
336
|
-
if (data.password) {
|
|
337
|
-
updateData.passwordHash = await resolvedOps.hashPassword(data.password);
|
|
338
|
-
}
|
|
339
|
-
const user = await repo.updateUser(id, updateData);
|
|
340
|
-
return user ? toAuthUserData(user) : null;
|
|
341
|
-
},
|
|
342
|
-
|
|
343
|
-
async deleteUser(id: string): Promise<void> {
|
|
344
|
-
// Call beforeUserDelete hook (throw to prevent deletion)
|
|
345
|
-
if (resolvedOps.beforeUserDelete) {
|
|
346
|
-
await resolvedOps.beforeUserDelete(id);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
await repo.deleteUser(id);
|
|
350
|
-
|
|
351
|
-
// Fire afterUserDelete hook (fire-and-forget)
|
|
352
|
-
if (resolvedOps.afterUserDelete) {
|
|
353
|
-
resolvedOps.afterUserDelete(id).catch(err => {
|
|
354
|
-
logger.error("[AuthHooks] afterUserDelete error", { error: err instanceof Error ? err.message : err });
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
},
|
|
358
|
-
|
|
359
|
-
async getUserRoles(userId: string): Promise<string[]> {
|
|
360
|
-
return repo.getUserRoleIds(userId);
|
|
361
|
-
},
|
|
362
|
-
|
|
363
|
-
async setUserRoles(userId: string, roleIds: string[]): Promise<void> {
|
|
364
|
-
await repo.setUserRoles(userId, roleIds);
|
|
365
|
-
}
|
|
366
|
-
};
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
function toAuthUserData(user: { id: string; email: string; displayName?: string | null; photoUrl?: string | null; emailVerified?: boolean; metadata?: Record<string, unknown>; createdAt?: Date; updatedAt?: Date }): AuthUserData {
|
|
370
|
-
return {
|
|
371
|
-
id: user.id,
|
|
372
|
-
email: user.email,
|
|
373
|
-
displayName: user.displayName,
|
|
374
|
-
photoUrl: user.photoUrl,
|
|
375
|
-
emailVerified: user.emailVerified,
|
|
376
|
-
metadata: user.metadata,
|
|
377
|
-
createdAt: user.createdAt,
|
|
378
|
-
updatedAt: user.updatedAt
|
|
379
|
-
};
|
|
380
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Custom Auth Adapter Factory
|
|
3
|
-
*
|
|
4
|
-
* Provides a minimal-config way for users with existing auth systems
|
|
5
|
-
* to plug into Rebase. Only `verifyRequest` is required — everything
|
|
6
|
-
* else is optional.
|
|
7
|
-
*
|
|
8
|
-
* @example
|
|
9
|
-
* ```ts
|
|
10
|
-
* import { createCustomAuthAdapter } from "@rebasepro/server-core";
|
|
11
|
-
*
|
|
12
|
-
* const auth = createCustomAuthAdapter({
|
|
13
|
-
* verifyRequest: async (request) => {
|
|
14
|
-
* const token = request.headers.get("Authorization")?.replace("Bearer ", "");
|
|
15
|
-
* if (!token) return null;
|
|
16
|
-
* const decoded = jwt.verify(token, MY_SECRET);
|
|
17
|
-
* return {
|
|
18
|
-
* uid: decoded.sub,
|
|
19
|
-
* email: decoded.email,
|
|
20
|
-
* displayName: decoded.name,
|
|
21
|
-
* roles: decoded.roles || [],
|
|
22
|
-
* isAdmin: decoded.roles?.includes("admin") ?? false,
|
|
23
|
-
* };
|
|
24
|
-
* },
|
|
25
|
-
* });
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import type {
|
|
30
|
-
AuthAdapter,
|
|
31
|
-
AuthAdapterCapabilities,
|
|
32
|
-
CustomAuthAdapterOptions
|
|
33
|
-
} from "@rebasepro/types";
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Create a custom auth adapter from minimal options.
|
|
37
|
-
*
|
|
38
|
-
* This is the recommended entry point for users who already have their own
|
|
39
|
-
* auth system and just need to tell Rebase how to extract the user from
|
|
40
|
-
* incoming requests.
|
|
41
|
-
*
|
|
42
|
-
* @param options - Configuration options. Only `verifyRequest` is required.
|
|
43
|
-
* @returns A fully-formed `AuthAdapter` ready for `initializeRebaseBackend()`.
|
|
44
|
-
*/
|
|
45
|
-
export function createCustomAuthAdapter(options: CustomAuthAdapterOptions): AuthAdapter {
|
|
46
|
-
const defaultCapabilities: AuthAdapterCapabilities = {
|
|
47
|
-
hasBuiltInAuthRoutes: false,
|
|
48
|
-
emailPasswordLogin: false,
|
|
49
|
-
registration: false,
|
|
50
|
-
passwordReset: false,
|
|
51
|
-
sessionManagement: false,
|
|
52
|
-
profileUpdate: false,
|
|
53
|
-
emailVerification: false,
|
|
54
|
-
magicLink: false,
|
|
55
|
-
enabledProviders: [],
|
|
56
|
-
...options.capabilities
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const resolvedVerifyToken = options.verifyToken
|
|
60
|
-
?? (async (token: string) => {
|
|
61
|
-
// Synthesize a minimal Request so adapters that only implement
|
|
62
|
-
// verifyRequest still work for WebSocket token verification.
|
|
63
|
-
const syntheticRequest = new Request("http://localhost/_ws_auth", {
|
|
64
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
65
|
-
});
|
|
66
|
-
return options.verifyRequest(syntheticRequest);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
return {
|
|
70
|
-
id: "custom",
|
|
71
|
-
|
|
72
|
-
serviceKey: options.serviceKey,
|
|
73
|
-
|
|
74
|
-
verifyRequest: options.verifyRequest,
|
|
75
|
-
|
|
76
|
-
verifyToken: resolvedVerifyToken,
|
|
77
|
-
|
|
78
|
-
userManagement: options.userManagement,
|
|
79
|
-
|
|
80
|
-
transformAuthResponse: options.transformAuthResponse,
|
|
81
|
-
|
|
82
|
-
getCapabilities() {
|
|
83
|
-
return defaultCapabilities;
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import type { OAuthProvider, OAuthProviderProfile } from "./interfaces";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { logger } from "../utils/logger";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Creates a Discord OAuth2 Provider integration.
|
|
7
|
-
*
|
|
8
|
-
* Uses the authorization code flow. Requires the "identify" and "email"
|
|
9
|
-
* scopes to retrieve the user's email and profile information.
|
|
10
|
-
*/
|
|
11
|
-
export function createDiscordProvider(config: { clientId: string; clientSecret: string }): OAuthProvider<{ code: string; redirectUri: string }> {
|
|
12
|
-
return {
|
|
13
|
-
id: "discord",
|
|
14
|
-
schema: z.object({
|
|
15
|
-
code: z.string().min(1, "Auth code is required"),
|
|
16
|
-
redirectUri: z.string().url("Valid redirect URI is required")
|
|
17
|
-
}),
|
|
18
|
-
verify: async (payload: { code: string; redirectUri: string }): Promise<OAuthProviderProfile | null> => {
|
|
19
|
-
try {
|
|
20
|
-
// Exchange code for access token
|
|
21
|
-
const tokenResponse = await fetch("https://discord.com/api/v10/oauth2/token", {
|
|
22
|
-
method: "POST",
|
|
23
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
24
|
-
body: new URLSearchParams({
|
|
25
|
-
client_id: config.clientId,
|
|
26
|
-
client_secret: config.clientSecret,
|
|
27
|
-
grant_type: "authorization_code",
|
|
28
|
-
code: payload.code,
|
|
29
|
-
redirect_uri: payload.redirectUri
|
|
30
|
-
})
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
if (!tokenResponse.ok) {
|
|
34
|
-
logger.error("Failed to get Discord access token", { detail: await tokenResponse.text() });
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const tokenData = await tokenResponse.json() as { access_token: string };
|
|
39
|
-
const accessToken = tokenData.access_token;
|
|
40
|
-
|
|
41
|
-
// Fetch user profile
|
|
42
|
-
const profileResponse = await fetch("https://discord.com/api/v10/users/@me", {
|
|
43
|
-
headers: { "Authorization": `Bearer ${accessToken}` }
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
if (!profileResponse.ok) {
|
|
47
|
-
logger.error("Failed to get Discord user info", { detail: await profileResponse.text() });
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const profileData = await profileResponse.json() as {
|
|
52
|
-
id: string;
|
|
53
|
-
username: string;
|
|
54
|
-
global_name?: string | null;
|
|
55
|
-
avatar?: string | null;
|
|
56
|
-
email?: string | null;
|
|
57
|
-
verified?: boolean;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
if (!profileData.email) {
|
|
61
|
-
logger.error("Discord user has no email (email scope may not have been granted)");
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Build avatar URL
|
|
66
|
-
let photoUrl: string | null = null;
|
|
67
|
-
if (profileData.avatar) {
|
|
68
|
-
const ext = profileData.avatar.startsWith("a_") ? "gif" : "png";
|
|
69
|
-
photoUrl = `https://cdn.discordapp.com/avatars/${profileData.id}/${profileData.avatar}.${ext}?size=256`;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
providerId: profileData.id,
|
|
74
|
-
email: profileData.email,
|
|
75
|
-
displayName: profileData.global_name || profileData.username || null,
|
|
76
|
-
photoUrl,
|
|
77
|
-
emailVerified: profileData.verified === true
|
|
78
|
-
};
|
|
79
|
-
} catch (error) {
|
|
80
|
-
logger.error("Discord OAuth error", { error: error });
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import type { OAuthProvider, OAuthProviderProfile } from "./interfaces";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { logger } from "../utils/logger";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Creates a Facebook / Meta OAuth Provider integration.
|
|
7
|
-
*
|
|
8
|
-
* Uses the authorization code flow to exchange a code for an access token,
|
|
9
|
-
* then fetches user profile from the Facebook Graph API.
|
|
10
|
-
*/
|
|
11
|
-
export function createFacebookProvider(config: { clientId: string; clientSecret: string }): OAuthProvider<{ code: string; redirectUri: string }> {
|
|
12
|
-
return {
|
|
13
|
-
id: "facebook",
|
|
14
|
-
schema: z.object({
|
|
15
|
-
code: z.string().min(1, "Auth code is required"),
|
|
16
|
-
redirectUri: z.string().url("Valid redirect URI is required")
|
|
17
|
-
}),
|
|
18
|
-
verify: async (payload: { code: string; redirectUri: string }): Promise<OAuthProviderProfile | null> => {
|
|
19
|
-
try {
|
|
20
|
-
// Exchange code for access token
|
|
21
|
-
const tokenUrl = new URL("https://graph.facebook.com/v19.0/oauth/access_token");
|
|
22
|
-
tokenUrl.searchParams.set("client_id", config.clientId);
|
|
23
|
-
tokenUrl.searchParams.set("client_secret", config.clientSecret);
|
|
24
|
-
tokenUrl.searchParams.set("redirect_uri", payload.redirectUri);
|
|
25
|
-
tokenUrl.searchParams.set("code", payload.code);
|
|
26
|
-
|
|
27
|
-
const tokenResponse = await fetch(tokenUrl.toString());
|
|
28
|
-
|
|
29
|
-
if (!tokenResponse.ok) {
|
|
30
|
-
logger.error("Failed to get Facebook access token", { detail: await tokenResponse.text() });
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const tokenData = await tokenResponse.json() as { access_token: string };
|
|
35
|
-
const accessToken = tokenData.access_token;
|
|
36
|
-
|
|
37
|
-
// Fetch user profile with email and profile picture
|
|
38
|
-
const profileUrl = new URL("https://graph.facebook.com/v19.0/me");
|
|
39
|
-
profileUrl.searchParams.set("fields", "id,name,email,picture.type(large)");
|
|
40
|
-
profileUrl.searchParams.set("access_token", accessToken);
|
|
41
|
-
|
|
42
|
-
const profileResponse = await fetch(profileUrl.toString());
|
|
43
|
-
|
|
44
|
-
if (!profileResponse.ok) {
|
|
45
|
-
logger.error("Failed to get Facebook user info", { detail: await profileResponse.text() });
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const profileData = await profileResponse.json() as {
|
|
50
|
-
id: string;
|
|
51
|
-
name?: string;
|
|
52
|
-
email?: string;
|
|
53
|
-
picture?: { data?: { url?: string } };
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
if (!profileData.email) {
|
|
57
|
-
logger.error("Facebook user has no email (email permission may not have been granted)");
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
providerId: profileData.id,
|
|
63
|
-
email: profileData.email,
|
|
64
|
-
displayName: profileData.name || null,
|
|
65
|
-
photoUrl: profileData.picture?.data?.url || null,
|
|
66
|
-
emailVerified: true
|
|
67
|
-
};
|
|
68
|
-
} catch (error) {
|
|
69
|
-
logger.error("Facebook OAuth error", { error: error });
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
}
|