@rebasepro/server-core 0.0.1-canary.4d4fb3e → 0.0.1-canary.ca2cb6e
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/dist/common/src/collections/CollectionRegistry.d.ts +8 -0
- package/dist/common/src/util/entities.d.ts +22 -0
- package/dist/common/src/util/relations.d.ts +14 -4
- package/dist/common/src/util/resolutions.d.ts +1 -1
- package/dist/index-DXVBFp5V.js +37 -0
- package/dist/index-DXVBFp5V.js.map +1 -0
- package/dist/index.es.js +49931 -55
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +28475 -34569
- package/dist/index.umd.js.map +1 -1
- package/dist/server-core/src/api/openapi-generator.d.ts +15 -1
- package/dist/server-core/src/api/types.d.ts +1 -1
- package/dist/server-core/src/auth/admin-routes.d.ts +10 -1
- package/dist/server-core/src/auth/apple-oauth.d.ts +30 -0
- package/dist/server-core/src/auth/bitbucket-oauth.d.ts +11 -0
- package/dist/server-core/src/auth/discord-oauth.d.ts +14 -0
- package/dist/server-core/src/auth/facebook-oauth.d.ts +14 -0
- package/dist/server-core/src/auth/github-oauth.d.ts +15 -0
- package/dist/server-core/src/auth/gitlab-oauth.d.ts +13 -0
- package/dist/server-core/src/auth/google-oauth.d.ts +5 -11
- package/dist/server-core/src/auth/index.d.ts +13 -2
- package/dist/server-core/src/auth/interfaces.d.ts +45 -6
- package/dist/server-core/src/auth/jwt.d.ts +1 -0
- package/dist/server-core/src/auth/linkedin-oauth.d.ts +18 -0
- package/dist/server-core/src/auth/microsoft-oauth.d.ts +16 -0
- package/dist/server-core/src/auth/middleware.d.ts +36 -11
- package/dist/server-core/src/auth/rate-limiter.d.ts +2 -2
- package/dist/server-core/src/auth/routes.d.ts +13 -3
- package/dist/server-core/src/auth/slack-oauth.d.ts +12 -0
- package/dist/server-core/src/auth/spotify-oauth.d.ts +12 -0
- package/dist/server-core/src/auth/twitter-oauth.d.ts +18 -0
- package/dist/server-core/src/cron/cron-loader.d.ts +17 -0
- package/dist/server-core/src/cron/cron-routes.d.ts +14 -0
- package/dist/server-core/src/cron/cron-scheduler.d.ts +61 -0
- package/dist/server-core/src/cron/cron-store.d.ts +32 -0
- package/dist/server-core/src/cron/index.d.ts +6 -0
- package/dist/server-core/src/email/index.d.ts +2 -2
- package/dist/server-core/src/email/templates.d.ts +9 -0
- package/dist/server-core/src/email/types.d.ts +20 -23
- package/dist/server-core/src/history/history-routes.d.ts +2 -2
- package/dist/server-core/src/index.d.ts +5 -0
- package/dist/server-core/src/init.d.ts +112 -2
- package/dist/server-core/src/singleton.d.ts +35 -0
- package/dist/server-core/src/storage/LocalStorageController.d.ts +9 -9
- package/dist/server-core/src/storage/S3StorageController.d.ts +8 -8
- package/dist/server-core/src/storage/index.d.ts +18 -11
- package/dist/server-core/src/storage/routes.d.ts +3 -3
- package/dist/server-core/src/storage/types.d.ts +28 -16
- package/dist/server-core/src/utils/dev-port.d.ts +35 -0
- package/dist/server-core/src/utils/logger.d.ts +31 -0
- package/dist/server-core/src/utils/request-logger.d.ts +19 -0
- package/dist/types/src/controllers/auth.d.ts +2 -0
- package/dist/types/src/controllers/client.d.ts +119 -7
- package/dist/types/src/controllers/collection_registry.d.ts +4 -3
- package/dist/types/src/controllers/customization_controller.d.ts +7 -1
- package/dist/types/src/controllers/data.d.ts +34 -7
- package/dist/types/src/controllers/data_driver.d.ts +20 -28
- package/dist/types/src/controllers/database_admin.d.ts +2 -2
- package/dist/types/src/controllers/email.d.ts +34 -0
- package/dist/types/src/controllers/index.d.ts +1 -0
- package/dist/types/src/controllers/local_config_persistence.d.ts +4 -4
- package/dist/types/src/controllers/navigation.d.ts +5 -5
- package/dist/types/src/controllers/registry.d.ts +6 -3
- package/dist/types/src/controllers/side_entity_controller.d.ts +7 -6
- package/dist/types/src/controllers/storage.d.ts +24 -26
- package/dist/types/src/rebase_context.d.ts +8 -4
- package/dist/types/src/types/backend.d.ts +4 -1
- package/dist/types/src/types/builders.d.ts +5 -4
- package/dist/types/src/types/chips.d.ts +1 -1
- package/dist/types/src/types/collections.d.ts +169 -125
- package/dist/types/src/types/cron.d.ts +102 -0
- package/dist/types/src/types/data_source.d.ts +1 -1
- package/dist/types/src/types/entity_actions.d.ts +8 -8
- package/dist/types/src/types/entity_callbacks.d.ts +15 -15
- package/dist/types/src/types/entity_link_builder.d.ts +1 -1
- package/dist/types/src/types/entity_overrides.d.ts +2 -1
- package/dist/types/src/types/entity_views.d.ts +8 -8
- package/dist/types/src/types/export_import.d.ts +3 -3
- package/dist/types/src/types/index.d.ts +1 -0
- package/dist/types/src/types/plugins.d.ts +72 -18
- package/dist/types/src/types/properties.d.ts +118 -33
- package/dist/types/src/types/relations.d.ts +1 -1
- package/dist/types/src/types/slots.d.ts +30 -6
- package/dist/types/src/types/translations.d.ts +44 -0
- package/dist/types/src/types/user_management_delegate.d.ts +1 -0
- package/package.json +84 -84
- package/scratch.ts +6 -5
- package/src/api/ast-schema-editor.ts +11 -11
- package/src/api/collections_for_test/callbacks_test_collection.ts +5 -2
- package/src/api/errors.ts +38 -14
- package/src/api/graphql/graphql-schema-generator.ts +5 -3
- package/src/api/openapi-generator.ts +613 -58
- package/src/api/rest/api-generator.ts +17 -11
- package/src/api/rest/query-parser.ts +1 -1
- package/src/api/schema-editor-routes.ts +2 -0
- package/src/api/server.ts +8 -5
- package/src/api/types.ts +1 -1
- package/src/auth/admin-routes.ts +58 -17
- package/src/auth/apple-oauth.ts +130 -0
- package/src/auth/bitbucket-oauth.ts +82 -0
- package/src/auth/discord-oauth.ts +83 -0
- package/src/auth/facebook-oauth.ts +72 -0
- package/src/auth/github-oauth.ts +110 -0
- package/src/auth/gitlab-oauth.ts +70 -0
- package/src/auth/google-oauth.ts +34 -46
- package/src/auth/index.ts +15 -2
- package/src/auth/interfaces.ts +54 -7
- package/src/auth/jwt.ts +22 -5
- package/src/auth/linkedin-oauth.ts +81 -0
- package/src/auth/microsoft-oauth.ts +88 -0
- package/src/auth/middleware.ts +194 -45
- package/src/auth/password.ts +3 -1
- package/src/auth/rate-limiter.ts +4 -4
- package/src/auth/routes.ts +197 -139
- package/src/auth/slack-oauth.ts +71 -0
- package/src/auth/spotify-oauth.ts +67 -0
- package/src/auth/twitter-oauth.ts +120 -0
- package/src/collections/loader.ts +6 -6
- package/src/cron/cron-loader.ts +89 -0
- package/src/cron/cron-routes.test.ts +265 -0
- package/src/cron/cron-routes.ts +85 -0
- package/src/cron/cron-scheduler.test.ts +421 -0
- package/src/cron/cron-scheduler.ts +413 -0
- package/src/cron/cron-store.ts +163 -0
- package/src/cron/index.ts +6 -0
- package/src/db/interfaces.ts +2 -2
- package/src/email/index.ts +3 -2
- package/src/email/smtp-email-service.ts +5 -2
- package/src/email/templates.ts +93 -6
- package/src/email/types.ts +17 -24
- package/src/functions/function-loader.ts +34 -6
- package/src/functions/function-routes.ts +2 -2
- package/src/history/history-routes.ts +4 -3
- package/src/index.ts +10 -0
- package/src/init.ts +475 -57
- package/src/services/driver-registry.ts +1 -1
- package/src/singleton.test.ts +28 -0
- package/src/singleton.ts +70 -0
- package/src/storage/LocalStorageController.ts +50 -53
- package/src/storage/S3StorageController.ts +49 -46
- package/src/storage/index.ts +27 -16
- package/src/storage/routes.ts +76 -59
- package/src/storage/storage-registry.ts +5 -5
- package/src/storage/types.ts +44 -32
- package/src/types/index.ts +4 -4
- package/src/utils/dev-port.ts +176 -0
- package/src/utils/logger.ts +143 -0
- package/src/utils/logging.ts +5 -2
- package/src/utils/request-logger.ts +66 -0
- package/test/admin-routes.test.ts +114 -65
- package/test/api-generator.test.ts +73 -30
- package/test/ast-schema-editor.test.ts +5 -3
- package/test/auth-middleware-hono.test.ts +268 -33
- package/test/auth-routes.test.ts +325 -146
- package/test/driver-registry.test.ts +4 -2
- package/test/error-propagation.test.ts +226 -0
- package/test/errors.test.ts +12 -7
- package/test/jwt-security.test.ts +18 -9
- package/test/jwt.test.ts +26 -13
- package/test/middleware.test.ts +13 -8
- package/test/query-parser.test.ts +13 -8
- package/test/rate-limiter.test.ts +7 -7
- package/test/safe-compare.test.ts +66 -0
- package/test/singleton.test.ts +59 -0
- package/test/storage-local.test.ts +36 -43
- package/test/storage-registry.test.ts +38 -36
- package/test/storage-routes.test.ts +9 -5
- package/test/storage-s3.test.ts +51 -48
- package/test.ts +6 -0
- package/tsconfig.json +1 -1
- package/vite.config.ts +7 -5
- package/dist/index-BeMqpmfQ.js +0 -239
- package/dist/index-BeMqpmfQ.js.map +0 -1
- package/dist/index-bl4J3lNb.js +0 -55823
- package/dist/index-bl4J3lNb.js.map +0 -1
- package/vite.config.ts.timestamp-1775065397568-8a853255edf6e.mjs +0 -46
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { OAuthProvider, OAuthProviderProfile } from "./interfaces";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a Facebook / Meta OAuth Provider integration.
|
|
6
|
+
*
|
|
7
|
+
* Uses the authorization code flow to exchange a code for an access token,
|
|
8
|
+
* then fetches user profile from the Facebook Graph API.
|
|
9
|
+
*/
|
|
10
|
+
export function createFacebookProvider(config: { clientId: string; clientSecret: string }): OAuthProvider<{ code: string; redirectUri: string }> {
|
|
11
|
+
return {
|
|
12
|
+
id: "facebook",
|
|
13
|
+
schema: z.object({
|
|
14
|
+
code: z.string().min(1, "Auth code is required"),
|
|
15
|
+
redirectUri: z.string().url("Valid redirect URI is required")
|
|
16
|
+
}),
|
|
17
|
+
verify: async (payload: { code: string; redirectUri: string }): Promise<OAuthProviderProfile | null> => {
|
|
18
|
+
try {
|
|
19
|
+
// Exchange code for access token
|
|
20
|
+
const tokenUrl = new URL("https://graph.facebook.com/v19.0/oauth/access_token");
|
|
21
|
+
tokenUrl.searchParams.set("client_id", config.clientId);
|
|
22
|
+
tokenUrl.searchParams.set("client_secret", config.clientSecret);
|
|
23
|
+
tokenUrl.searchParams.set("redirect_uri", payload.redirectUri);
|
|
24
|
+
tokenUrl.searchParams.set("code", payload.code);
|
|
25
|
+
|
|
26
|
+
const tokenResponse = await fetch(tokenUrl.toString());
|
|
27
|
+
|
|
28
|
+
if (!tokenResponse.ok) {
|
|
29
|
+
console.error("Failed to get Facebook access token:", await tokenResponse.text());
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const tokenData = await tokenResponse.json() as { access_token: string };
|
|
34
|
+
const accessToken = tokenData.access_token;
|
|
35
|
+
|
|
36
|
+
// Fetch user profile with email and profile picture
|
|
37
|
+
const profileUrl = new URL("https://graph.facebook.com/v19.0/me");
|
|
38
|
+
profileUrl.searchParams.set("fields", "id,name,email,picture.type(large)");
|
|
39
|
+
profileUrl.searchParams.set("access_token", accessToken);
|
|
40
|
+
|
|
41
|
+
const profileResponse = await fetch(profileUrl.toString());
|
|
42
|
+
|
|
43
|
+
if (!profileResponse.ok) {
|
|
44
|
+
console.error("Failed to get Facebook user info:", await profileResponse.text());
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const profileData = await profileResponse.json() as {
|
|
49
|
+
id: string;
|
|
50
|
+
name?: string;
|
|
51
|
+
email?: string;
|
|
52
|
+
picture?: { data?: { url?: string } };
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (!profileData.email) {
|
|
56
|
+
console.error("Facebook user has no email (email permission may not have been granted)");
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
providerId: profileData.id,
|
|
62
|
+
email: profileData.email,
|
|
63
|
+
displayName: profileData.name || null,
|
|
64
|
+
photoUrl: profileData.picture?.data?.url || null
|
|
65
|
+
};
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error("Facebook OAuth error:", error);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { OAuthProvider, OAuthProviderProfile } from "./interfaces";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a GitHub OAuth Provider integration.
|
|
6
|
+
*
|
|
7
|
+
* Flow: Frontend receives an authorization `code` via the GitHub OAuth redirect.
|
|
8
|
+
* This provider exchanges the code for an access token, then fetches the user's
|
|
9
|
+
* profile and primary email from the GitHub API.
|
|
10
|
+
*/
|
|
11
|
+
export function createGitHubProvider(config: { clientId: string; clientSecret: string }): OAuthProvider<{ code: string; redirectUri: string }> {
|
|
12
|
+
return {
|
|
13
|
+
id: "github",
|
|
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://github.com/login/oauth/access_token", {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: {
|
|
24
|
+
"Content-Type": "application/json",
|
|
25
|
+
"Accept": "application/json"
|
|
26
|
+
},
|
|
27
|
+
body: JSON.stringify({
|
|
28
|
+
client_id: config.clientId,
|
|
29
|
+
client_secret: config.clientSecret,
|
|
30
|
+
code: payload.code,
|
|
31
|
+
redirect_uri: payload.redirectUri
|
|
32
|
+
})
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (!tokenResponse.ok) {
|
|
36
|
+
console.error("Failed to get GitHub access token:", await tokenResponse.text());
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const tokenData = await tokenResponse.json() as { access_token?: string; error?: string };
|
|
41
|
+
if (tokenData.error || !tokenData.access_token) {
|
|
42
|
+
console.error("GitHub token exchange error:", tokenData.error);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const accessToken = tokenData.access_token;
|
|
47
|
+
|
|
48
|
+
// Fetch user profile
|
|
49
|
+
const profileResponse = await fetch("https://api.github.com/user", {
|
|
50
|
+
headers: {
|
|
51
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
52
|
+
"Accept": "application/vnd.github+json",
|
|
53
|
+
"User-Agent": "Rebase-Auth"
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
if (!profileResponse.ok) {
|
|
58
|
+
console.error("Failed to get GitHub user info:", await profileResponse.text());
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const profileData = await profileResponse.json() as {
|
|
63
|
+
id: number;
|
|
64
|
+
login: string;
|
|
65
|
+
name?: string | null;
|
|
66
|
+
avatar_url?: string | null;
|
|
67
|
+
email?: string | null;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// GitHub may not return email in profile if it's private.
|
|
71
|
+
// Fetch from /user/emails endpoint instead.
|
|
72
|
+
let email = profileData.email;
|
|
73
|
+
if (!email) {
|
|
74
|
+
const emailsResponse = await fetch("https://api.github.com/user/emails", {
|
|
75
|
+
headers: {
|
|
76
|
+
"Authorization": `Bearer ${accessToken}`,
|
|
77
|
+
"Accept": "application/vnd.github+json",
|
|
78
|
+
"User-Agent": "Rebase-Auth"
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (emailsResponse.ok) {
|
|
83
|
+
const emails = await emailsResponse.json() as Array<{
|
|
84
|
+
email: string;
|
|
85
|
+
primary: boolean;
|
|
86
|
+
verified: boolean;
|
|
87
|
+
}>;
|
|
88
|
+
const primary = emails.find(e => e.primary && e.verified) || emails.find(e => e.verified);
|
|
89
|
+
email = primary?.email || null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!email) {
|
|
94
|
+
console.error("GitHub user has no verified email");
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
providerId: String(profileData.id),
|
|
100
|
+
email,
|
|
101
|
+
displayName: profileData.name || profileData.login || null,
|
|
102
|
+
photoUrl: profileData.avatar_url || null
|
|
103
|
+
};
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error("GitHub OAuth error:", error);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { OAuthProvider, OAuthProviderProfile } from "./interfaces";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates a GitLab OAuth Provider integration.
|
|
6
|
+
* Works with both GitLab.com and self-hosted instances.
|
|
7
|
+
*/
|
|
8
|
+
export function createGitLabProvider(config: {
|
|
9
|
+
clientId: string;
|
|
10
|
+
clientSecret: string;
|
|
11
|
+
baseUrl?: string;
|
|
12
|
+
}): OAuthProvider<{ code: string; redirectUri: string }> {
|
|
13
|
+
const gitlabUrl = (config.baseUrl || "https://gitlab.com").replace(/\/$/, "");
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
id: "gitlab",
|
|
17
|
+
schema: z.object({
|
|
18
|
+
code: z.string().min(1, "Auth code is required"),
|
|
19
|
+
redirectUri: z.string().url("Valid redirect URI is required")
|
|
20
|
+
}),
|
|
21
|
+
verify: async (payload: { code: string; redirectUri: string }): Promise<OAuthProviderProfile | null> => {
|
|
22
|
+
try {
|
|
23
|
+
const tokenResponse = await fetch(`${gitlabUrl}/oauth/token`, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: { "Content-Type": "application/json" },
|
|
26
|
+
body: JSON.stringify({
|
|
27
|
+
client_id: config.clientId,
|
|
28
|
+
client_secret: config.clientSecret,
|
|
29
|
+
code: payload.code,
|
|
30
|
+
grant_type: "authorization_code",
|
|
31
|
+
redirect_uri: payload.redirectUri
|
|
32
|
+
})
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (!tokenResponse.ok) {
|
|
36
|
+
console.error("Failed to get GitLab access token:", await tokenResponse.text());
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const tokenData = await tokenResponse.json() as { access_token: string };
|
|
41
|
+
|
|
42
|
+
const profileResponse = await fetch(`${gitlabUrl}/api/v4/user`, {
|
|
43
|
+
headers: { "Authorization": `Bearer ${tokenData.access_token}` }
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!profileResponse.ok) {
|
|
47
|
+
console.error("Failed to get GitLab user info:", await profileResponse.text());
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const p = await profileResponse.json() as {
|
|
52
|
+
id: number; username: string; name?: string;
|
|
53
|
+
email: string; avatar_url?: string | null;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (!p.email) { console.error("GitLab user has no email"); return null; }
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
providerId: String(p.id),
|
|
60
|
+
email: p.email,
|
|
61
|
+
displayName: p.name || p.username || null,
|
|
62
|
+
photoUrl: p.avatar_url || null
|
|
63
|
+
};
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error("GitLab OAuth error:", error);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
}
|
package/src/auth/google-oauth.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { OAuth2Client } from "google-auth-library/build/src/index.js";
|
|
2
|
+
import type { OAuthProvider, OAuthProviderProfile } from "./interfaces";
|
|
3
|
+
import { z } from "zod";
|
|
2
4
|
|
|
3
5
|
export interface GoogleUserInfo {
|
|
4
6
|
googleId: string;
|
|
@@ -8,53 +10,39 @@ export interface GoogleUserInfo {
|
|
|
8
10
|
emailVerified: boolean;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
let googleClient: OAuth2Client | null = null;
|
|
12
|
-
let configuredClientId: string | null = null;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Configure Google OAuth - call this during initialization
|
|
16
|
-
*/
|
|
17
|
-
export function configureGoogleOAuth(clientId: string): void {
|
|
18
|
-
configuredClientId = clientId;
|
|
19
|
-
googleClient = new OAuth2Client(clientId);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
13
|
/**
|
|
23
|
-
*
|
|
24
|
-
* @param idToken The ID token from Google Sign-In on the frontend
|
|
14
|
+
* Creates a Google OAuth Provider integration
|
|
25
15
|
*/
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
16
|
+
export function createGoogleProvider(clientId: string): OAuthProvider<{ idToken: string }> {
|
|
17
|
+
const googleClient = new OAuth2Client(clientId);
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
id: "google",
|
|
21
|
+
schema: z.object({
|
|
22
|
+
idToken: z.string().min(1, "ID token is required")
|
|
23
|
+
}),
|
|
24
|
+
verify: async (payload: { idToken: string }): Promise<OAuthProviderProfile | null> => {
|
|
25
|
+
try {
|
|
26
|
+
const ticket = await googleClient.verifyIdToken({
|
|
27
|
+
idToken: payload.idToken,
|
|
28
|
+
audience: clientId
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const content = ticket.getPayload();
|
|
32
|
+
if (!content) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
providerId: content.sub,
|
|
38
|
+
email: content.email || "",
|
|
39
|
+
displayName: content.name || null,
|
|
40
|
+
photoUrl: content.picture || null
|
|
41
|
+
};
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error("Failed to verify Google ID token:", error);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
40
46
|
}
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
googleId: payload.sub,
|
|
44
|
-
email: payload.email || "",
|
|
45
|
-
displayName: payload.name || null,
|
|
46
|
-
photoUrl: payload.picture || null,
|
|
47
|
-
emailVerified: payload.email_verified || false
|
|
48
|
-
};
|
|
49
|
-
} catch (error) {
|
|
50
|
-
console.error("Failed to verify Google ID token:", error);
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Check if Google OAuth is configured
|
|
57
|
-
*/
|
|
58
|
-
export function isGoogleOAuthConfigured(): boolean {
|
|
59
|
-
return googleClient !== null && configuredClientId !== null;
|
|
47
|
+
};
|
|
60
48
|
}
|
package/src/auth/index.ts
CHANGED
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
// Auth module exports
|
|
2
|
+
export * from "./interfaces";
|
|
3
|
+
|
|
2
4
|
export { configureJwt, generateAccessToken, verifyAccessToken, generateRefreshToken, hashRefreshToken, getRefreshTokenExpiry, getAccessTokenExpiry } from "./jwt";
|
|
3
5
|
export type { JwtConfig, AccessTokenPayload } from "./jwt";
|
|
4
6
|
|
|
5
7
|
export { hashPassword, verifyPassword, validatePasswordStrength } from "./password";
|
|
6
8
|
export type { PasswordValidationResult } from "./password";
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
export
|
|
10
|
+
// OAuth Providers
|
|
11
|
+
export { createGoogleProvider } from "./google-oauth";
|
|
12
|
+
export { createLinkedinProvider } from "./linkedin-oauth";
|
|
13
|
+
export { createGitHubProvider } from "./github-oauth";
|
|
14
|
+
export { createMicrosoftProvider } from "./microsoft-oauth";
|
|
15
|
+
export { createAppleProvider } from "./apple-oauth";
|
|
16
|
+
export { createFacebookProvider } from "./facebook-oauth";
|
|
17
|
+
export { createTwitterProvider } from "./twitter-oauth";
|
|
18
|
+
export { createDiscordProvider } from "./discord-oauth";
|
|
19
|
+
export { createGitLabProvider } from "./gitlab-oauth";
|
|
20
|
+
export { createBitbucketProvider } from "./bitbucket-oauth";
|
|
21
|
+
export { createSlackProvider } from "./slack-oauth";
|
|
22
|
+
export { createSpotifyProvider } from "./spotify-oauth";
|
|
10
23
|
|
|
11
24
|
export { requireAuth, requireAdmin, optionalAuth, extractUserFromToken, createAuthMiddleware } from "./middleware";
|
|
12
25
|
export type { AuthMiddlewareOptions, AuthResult } from "./middleware";
|
package/src/auth/interfaces.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Authentication Abstraction Interfaces
|
|
3
|
-
*
|
|
5
|
+
*
|
|
4
6
|
* These interfaces define the contracts for authentication-related operations.
|
|
5
7
|
* Implementations can use different databases (PostgreSQL, MongoDB, etc.) to
|
|
6
8
|
* store user, role, and token data.
|
|
@@ -15,8 +17,6 @@ export interface UserData {
|
|
|
15
17
|
passwordHash?: string | null;
|
|
16
18
|
displayName?: string | null;
|
|
17
19
|
photoUrl?: string | null;
|
|
18
|
-
provider: string;
|
|
19
|
-
googleId?: string | null;
|
|
20
20
|
emailVerified: boolean;
|
|
21
21
|
emailVerificationToken?: string | null;
|
|
22
22
|
emailVerificationSentAt?: Date | null;
|
|
@@ -32,11 +32,46 @@ export interface CreateUserData {
|
|
|
32
32
|
passwordHash?: string;
|
|
33
33
|
displayName?: string;
|
|
34
34
|
photoUrl?: string;
|
|
35
|
-
provider?: string;
|
|
36
|
-
googleId?: string;
|
|
37
35
|
emailVerified?: boolean;
|
|
38
36
|
}
|
|
39
37
|
|
|
38
|
+
/**
|
|
39
|
+
* User Identity Data (OAuth accounts linked to user)
|
|
40
|
+
*/
|
|
41
|
+
export interface UserIdentityData {
|
|
42
|
+
id: string;
|
|
43
|
+
userId: string;
|
|
44
|
+
provider: string;
|
|
45
|
+
providerId: string;
|
|
46
|
+
profileData?: Record<string, unknown> | null;
|
|
47
|
+
createdAt: Date;
|
|
48
|
+
updatedAt: Date;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Standardized profile data returned by an OAuth provider verification payload
|
|
53
|
+
*/
|
|
54
|
+
export interface OAuthProviderProfile {
|
|
55
|
+
providerId: string;
|
|
56
|
+
email: string;
|
|
57
|
+
displayName?: string | null;
|
|
58
|
+
photoUrl?: string | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Pluggable OAuth Provider integration strategy
|
|
63
|
+
*/
|
|
64
|
+
export interface OAuthProvider<T = unknown> {
|
|
65
|
+
/** The identifier of the provider (e.g. "github", "google") */
|
|
66
|
+
id: string;
|
|
67
|
+
|
|
68
|
+
/** Zod schema validating the expected request payload (e.g. { code: string }) */
|
|
69
|
+
schema: z.ZodSchema<T>;
|
|
70
|
+
|
|
71
|
+
/** Verify external tokens/codes and return a standardized user profile */
|
|
72
|
+
verify: (payload: T) => Promise<OAuthProviderProfile | null>;
|
|
73
|
+
}
|
|
74
|
+
|
|
40
75
|
/**
|
|
41
76
|
* Role data structure
|
|
42
77
|
*/
|
|
@@ -110,6 +145,8 @@ export interface ListUsersOptions {
|
|
|
110
145
|
orderBy?: string;
|
|
111
146
|
/** Sort direction (default "desc") */
|
|
112
147
|
orderDir?: "asc" | "desc";
|
|
148
|
+
/** Filter by role ID */
|
|
149
|
+
roleId?: string;
|
|
113
150
|
}
|
|
114
151
|
|
|
115
152
|
/**
|
|
@@ -144,9 +181,19 @@ export interface UserRepository {
|
|
|
144
181
|
getUserByEmail(email: string): Promise<UserData | null>;
|
|
145
182
|
|
|
146
183
|
/**
|
|
147
|
-
* Get a user by
|
|
184
|
+
* Get a user by an OAuth identity
|
|
185
|
+
*/
|
|
186
|
+
getUserByIdentity(provider: string, providerId: string): Promise<UserData | null>;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get all identities linked to a user
|
|
190
|
+
*/
|
|
191
|
+
getUserIdentities(userId: string): Promise<UserIdentityData[]>;
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Link a new OAuth identity to a user
|
|
148
195
|
*/
|
|
149
|
-
|
|
196
|
+
linkUserIdentity(userId: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void>;
|
|
150
197
|
|
|
151
198
|
/**
|
|
152
199
|
* Update a user
|
package/src/auth/jwt.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface JwtConfig {
|
|
|
10
10
|
export interface AccessTokenPayload {
|
|
11
11
|
userId: string;
|
|
12
12
|
roles: string[];
|
|
13
|
+
uid?: string;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
let jwtConfig: JwtConfig = {
|
|
@@ -30,10 +31,18 @@ export function configureJwt(config: JwtConfig): void {
|
|
|
30
31
|
"jwt_secret",
|
|
31
32
|
"your-secret",
|
|
32
33
|
"your-super-secret-jwt-key-change-in-production",
|
|
34
|
+
"super-secret-jwt-key-change-in-production",
|
|
33
35
|
"change-me",
|
|
34
36
|
"changeme",
|
|
35
37
|
"password",
|
|
36
|
-
"test"
|
|
38
|
+
"test",
|
|
39
|
+
"mysecret",
|
|
40
|
+
"my-secret",
|
|
41
|
+
"my_secret",
|
|
42
|
+
"example-secret",
|
|
43
|
+
"please-change-me",
|
|
44
|
+
"replace-this-with-a-real-secret",
|
|
45
|
+
"default-secret"
|
|
37
46
|
]);
|
|
38
47
|
|
|
39
48
|
if (!config.secret || config.secret.length < 32) {
|
|
@@ -64,7 +73,8 @@ export function generateAccessToken(userId: string, roles: string[]): string {
|
|
|
64
73
|
throw new Error("JWT secret not configured. Call configureJwt() first.");
|
|
65
74
|
}
|
|
66
75
|
|
|
67
|
-
const payload: AccessTokenPayload = { userId,
|
|
76
|
+
const payload: AccessTokenPayload = { userId,
|
|
77
|
+
roles };
|
|
68
78
|
|
|
69
79
|
return jwt.sign(payload, jwtConfig.secret, {
|
|
70
80
|
expiresIn: jwtConfig.accessExpiresIn as jwt.SignOptions["expiresIn"],
|
|
@@ -112,12 +122,19 @@ export function verifyAccessToken(token: string): AccessTokenPayload | null {
|
|
|
112
122
|
}
|
|
113
123
|
|
|
114
124
|
try {
|
|
115
|
-
const decoded = jwt.verify(token, jwtConfig.secret, { algorithms: ["HS256"] }) as
|
|
125
|
+
const decoded = jwt.verify(token, jwtConfig.secret, { algorithms: ["HS256"] }) as { userId?: string; uid?: string; sub?: string; roles?: string[] };
|
|
126
|
+
const id = decoded.userId || decoded.uid || decoded.sub;
|
|
127
|
+
if (!id) {
|
|
128
|
+
console.error("[JWT] Verification failed: missing id in payload", decoded);
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
116
132
|
return {
|
|
117
|
-
userId:
|
|
118
|
-
roles: decoded.roles
|
|
133
|
+
userId: id,
|
|
134
|
+
roles: decoded.roles || []
|
|
119
135
|
};
|
|
120
136
|
} catch (error) {
|
|
137
|
+
console.error("[JWT] Verification failed:", error, "Token start:", token.substring(0, 15));
|
|
121
138
|
return null;
|
|
122
139
|
}
|
|
123
140
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { OAuthProvider, OAuthProviderProfile } from "./interfaces";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
export interface LinkedinUserInfo {
|
|
5
|
+
linkedinId: string;
|
|
6
|
+
email: string;
|
|
7
|
+
displayName: string | null;
|
|
8
|
+
photoUrl: string | null;
|
|
9
|
+
emailVerified: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates a LinkedIn OAuth Provider integration
|
|
14
|
+
*/
|
|
15
|
+
export function createLinkedinProvider(config: { clientId: string, clientSecret: string }): OAuthProvider<{ code: string; redirectUri: string }> {
|
|
16
|
+
return {
|
|
17
|
+
id: "linkedin",
|
|
18
|
+
schema: z.object({
|
|
19
|
+
code: z.string().min(1, "Auth code is required"),
|
|
20
|
+
redirectUri: z.string().url("Valid redirect URI is required")
|
|
21
|
+
}),
|
|
22
|
+
verify: async (payload: { code: string; redirectUri: string }): Promise<OAuthProviderProfile | null> => {
|
|
23
|
+
try {
|
|
24
|
+
// Exchange code for access token
|
|
25
|
+
const tokenResponse = await fetch("https://www.linkedin.com/oauth/v2/accessToken", {
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers: {
|
|
28
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
29
|
+
},
|
|
30
|
+
body: new URLSearchParams({
|
|
31
|
+
grant_type: "authorization_code",
|
|
32
|
+
code: payload.code,
|
|
33
|
+
redirect_uri: payload.redirectUri,
|
|
34
|
+
client_id: config.clientId,
|
|
35
|
+
client_secret: config.clientSecret
|
|
36
|
+
})
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (!tokenResponse.ok) {
|
|
40
|
+
const errBody = await tokenResponse.text();
|
|
41
|
+
console.error("Failed to get LinkedIn access token:", errBody);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const tokenData = await tokenResponse.json() as { access_token: string; id_token?: string };
|
|
46
|
+
const accessToken = tokenData.access_token;
|
|
47
|
+
|
|
48
|
+
// Fetch User Info using OIDC userinfo endpoint
|
|
49
|
+
const profileResponse = await fetch("https://api.linkedin.com/v2/userinfo", {
|
|
50
|
+
headers: {
|
|
51
|
+
"Authorization": `Bearer ${accessToken}`
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (!profileResponse.ok) {
|
|
56
|
+
const errBody = await profileResponse.text();
|
|
57
|
+
console.error("Failed to get LinkedIn user info:", errBody);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const profileData = await profileResponse.json() as {
|
|
62
|
+
sub: string;
|
|
63
|
+
email: string;
|
|
64
|
+
name?: string;
|
|
65
|
+
picture?: string;
|
|
66
|
+
email_verified?: boolean;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
providerId: profileData.sub,
|
|
71
|
+
email: profileData.email,
|
|
72
|
+
displayName: profileData.name || null,
|
|
73
|
+
photoUrl: profileData.picture || null
|
|
74
|
+
};
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error("LinkedIn OAuth error:", error);
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|