@jskit-ai/auth-provider-supabase-core 0.1.156 → 0.1.158
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/package.json +13 -126
- package/patterns/supabase-auth/PATTERN.md +67 -0
- package/patterns/supabase-auth/example/.env.example +4 -0
- package/patterns/supabase-auth/example/config/server.js +11 -0
- package/patterns/supabase-auth/example/package.json +7 -0
- package/src/server/providers/AuthSupabaseProvider.js +127 -0
- package/test/entrypoints.boundary.test.js +1 -2
- package/test/providerRuntime.test.js +78 -681
- package/test/supabaseAuthPattern.test.js +16 -0
- package/src/server/providers/AuthProviderServiceProvider.js +0 -7
- package/src/server/providers/AuthSupabaseServiceProvider.js +0 -259
package/package.json
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/auth-provider-supabase-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.158",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
7
7
|
},
|
|
8
8
|
"exports": {
|
|
9
|
-
"./server/providers/
|
|
10
|
-
"./server/providers/AuthProviderServiceProvider": "./src/server/providers/AuthProviderServiceProvider.js",
|
|
9
|
+
"./server/providers/AuthSupabaseProvider": "./src/server/providers/AuthSupabaseProvider.js",
|
|
11
10
|
"./server/lib/index": "./src/server/lib/index.js",
|
|
12
11
|
"./client": "./src/client/index.js"
|
|
13
12
|
},
|
|
14
13
|
"dependencies": {
|
|
15
|
-
"@jskit-ai/auth-core": "0.1.
|
|
16
|
-
"@jskit-ai/kernel": "0.1.
|
|
14
|
+
"@jskit-ai/auth-core": "0.1.159",
|
|
15
|
+
"@jskit-ai/kernel": "0.1.161",
|
|
17
16
|
"@supabase/supabase-js": "^2.57.4",
|
|
18
17
|
"jose": "^6.1.0",
|
|
19
18
|
"json-rest-schema": "^1.0.17",
|
|
@@ -21,47 +20,22 @@
|
|
|
21
20
|
},
|
|
22
21
|
"jskit": {
|
|
23
22
|
"kind": "runtime",
|
|
24
|
-
"options": {
|
|
25
|
-
"auth-supabase-url": {
|
|
26
|
-
"required": true,
|
|
27
|
-
"values": [],
|
|
28
|
-
"promptLabel": "Supabase URL",
|
|
29
|
-
"promptHint": "https://YOUR-PROJECT.supabase.co"
|
|
30
|
-
},
|
|
31
|
-
"auth-supabase-publishable-key": {
|
|
32
|
-
"required": true,
|
|
33
|
-
"values": [],
|
|
34
|
-
"promptLabel": "Supabase publishable key",
|
|
35
|
-
"promptHint": "sb_publishable_..."
|
|
36
|
-
},
|
|
37
|
-
"app-public-url": {
|
|
38
|
-
"required": true,
|
|
39
|
-
"values": [],
|
|
40
|
-
"defaultValue": "http://localhost:5173",
|
|
41
|
-
"promptLabel": "App public URL",
|
|
42
|
-
"promptHint": "Browser URL used for auth redirects"
|
|
43
|
-
}
|
|
44
|
-
},
|
|
45
23
|
"capabilities": {
|
|
46
24
|
"provides": [
|
|
47
|
-
"auth.
|
|
48
|
-
"auth.provider"
|
|
25
|
+
"auth.service"
|
|
49
26
|
],
|
|
50
27
|
"requires": [
|
|
51
|
-
"auth.
|
|
28
|
+
"auth.extensions",
|
|
29
|
+
"runtime.config",
|
|
30
|
+
"runtime.env"
|
|
52
31
|
]
|
|
53
32
|
},
|
|
54
33
|
"runtime": {
|
|
55
34
|
"server": {
|
|
56
|
-
"providerEntrypoint": "src/server/providers/AuthSupabaseServiceProvider.js",
|
|
57
35
|
"providers": [
|
|
58
36
|
{
|
|
59
|
-
"entrypoint": "src/server/providers/
|
|
60
|
-
"export": "
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
"entrypoint": "src/server/providers/AuthProviderServiceProvider.js",
|
|
64
|
-
"export": "AuthProviderServiceProvider"
|
|
37
|
+
"entrypoint": "src/server/providers/AuthSupabaseProvider.js",
|
|
38
|
+
"export": "AuthSupabaseProvider"
|
|
65
39
|
}
|
|
66
40
|
]
|
|
67
41
|
}
|
|
@@ -70,12 +44,8 @@
|
|
|
70
44
|
"apiSummary": {
|
|
71
45
|
"surfaces": [
|
|
72
46
|
{
|
|
73
|
-
"subpath": "./server/providers/
|
|
74
|
-
"summary": "
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
"subpath": "./server/providers/AuthProviderServiceProvider",
|
|
78
|
-
"summary": "Exports the generic auth provider registration service provider."
|
|
47
|
+
"subpath": "./server/providers/AuthSupabaseProvider",
|
|
48
|
+
"summary": "Provides the selected Supabase authentication service through explicit runtime capabilities."
|
|
79
49
|
},
|
|
80
50
|
{
|
|
81
51
|
"subpath": "./server/lib/index",
|
|
@@ -86,91 +56,8 @@
|
|
|
86
56
|
"summary": "Exports no runtime API today (reserved client entrypoint)."
|
|
87
57
|
}
|
|
88
58
|
],
|
|
89
|
-
"
|
|
90
|
-
"server": [
|
|
91
|
-
"authService"
|
|
92
|
-
],
|
|
93
|
-
"client": []
|
|
94
|
-
}
|
|
59
|
+
"capability": "auth.service"
|
|
95
60
|
}
|
|
96
|
-
},
|
|
97
|
-
"mutations": {
|
|
98
|
-
"dependencies": {
|
|
99
|
-
"runtime": {
|
|
100
|
-
"@jskit-ai/auth-core": "0.1.157",
|
|
101
|
-
"@jskit-ai/kernel": "0.1.159",
|
|
102
|
-
"dotenv": "^16.4.5",
|
|
103
|
-
"@supabase/supabase-js": "^2.57.4",
|
|
104
|
-
"jose": "^6.1.0"
|
|
105
|
-
},
|
|
106
|
-
"dev": {}
|
|
107
|
-
},
|
|
108
|
-
"packageJson": {
|
|
109
|
-
"scripts": {}
|
|
110
|
-
},
|
|
111
|
-
"procfile": {},
|
|
112
|
-
"files": [],
|
|
113
|
-
"text": [
|
|
114
|
-
{
|
|
115
|
-
"file": ".env",
|
|
116
|
-
"op": "upsert-env",
|
|
117
|
-
"key": "AUTH_PROVIDER",
|
|
118
|
-
"value": "supabase",
|
|
119
|
-
"reason": "Select Supabase as the auth provider.",
|
|
120
|
-
"category": "runtime-config",
|
|
121
|
-
"id": "auth-provider"
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
"file": ".env",
|
|
125
|
-
"op": "upsert-env",
|
|
126
|
-
"key": "AUTH_SUPABASE_URL",
|
|
127
|
-
"value": "${option:auth-supabase-url}",
|
|
128
|
-
"reason": "Configure Supabase project URL for auth.",
|
|
129
|
-
"category": "runtime-config",
|
|
130
|
-
"id": "auth-supabase-url"
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
"file": ".env",
|
|
134
|
-
"op": "upsert-env",
|
|
135
|
-
"key": "AUTH_SUPABASE_PUBLISHABLE_KEY",
|
|
136
|
-
"value": "${option:auth-supabase-publishable-key}",
|
|
137
|
-
"reason": "Configure Supabase publishable key for auth.",
|
|
138
|
-
"category": "runtime-config",
|
|
139
|
-
"id": "auth-supabase-publishable-key"
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
"file": ".env",
|
|
143
|
-
"op": "upsert-env",
|
|
144
|
-
"key": "APP_PUBLIC_URL",
|
|
145
|
-
"value": "${option:app-public-url}",
|
|
146
|
-
"reason": "Configure application public URL for auth redirect flows.",
|
|
147
|
-
"category": "runtime-config",
|
|
148
|
-
"id": "auth-app-public-url"
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
"op": "append-text",
|
|
152
|
-
"file": "config/server.js",
|
|
153
|
-
"position": "bottom",
|
|
154
|
-
"skipIfContains": "config.auth.profileMode =",
|
|
155
|
-
"value": "\nconfig.auth ||= {};\nconfig.auth.profileMode = \"provider\";\n",
|
|
156
|
-
"reason": "Use provider-owned auth profiles until a users package enables users-backed sync.",
|
|
157
|
-
"category": "runtime-config",
|
|
158
|
-
"id": "auth-profile-mode-provider"
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
"op": "append-text",
|
|
162
|
-
"file": "config/server.js",
|
|
163
|
-
"position": "bottom",
|
|
164
|
-
"skipIfContains": [
|
|
165
|
-
"config.auth.oauth = {",
|
|
166
|
-
"config.auth = {\n oauth: {"
|
|
167
|
-
],
|
|
168
|
-
"value": "\nconfig.auth ||= {};\nconfig.auth.oauth = {\n providers: [],\n defaultProvider: \"\"\n};\n",
|
|
169
|
-
"reason": "Append app-owned OAuth provider visibility config for stock auth screens.",
|
|
170
|
-
"category": "runtime-config",
|
|
171
|
-
"id": "auth-oauth-app-config"
|
|
172
|
-
}
|
|
173
|
-
]
|
|
174
61
|
}
|
|
175
62
|
}
|
|
176
63
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: auth/supabase-auth
|
|
3
|
+
title: Supabase authentication
|
|
4
|
+
summary: Add Supabase authentication through normal npm composition, explicit environment values, and app-owned auth policy.
|
|
5
|
+
keywords: auth, authentication, oauth, sessions, supabase
|
|
6
|
+
requires: @jskit-ai/auth-provider-supabase-core
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Supabase authentication
|
|
10
|
+
|
|
11
|
+
## Use when
|
|
12
|
+
|
|
13
|
+
Use this pattern when the product deliberately chooses Supabase as its identity
|
|
14
|
+
provider. Install the runtime package normally and configure the application
|
|
15
|
+
environment and visible OAuth providers explicitly.
|
|
16
|
+
|
|
17
|
+
## Do not use when
|
|
18
|
+
|
|
19
|
+
Do not use this pattern for local credential storage, a custom identity
|
|
20
|
+
provider, or an application whose authentication decision is still open.
|
|
21
|
+
|
|
22
|
+
## Product decisions
|
|
23
|
+
|
|
24
|
+
Decide the public application URL, allowed sign-in methods, visible OAuth
|
|
25
|
+
providers, callback URLs, account-linking policy, and where deployment secrets
|
|
26
|
+
live. Obtain the project URL and publishable key from the selected Supabase
|
|
27
|
+
project.
|
|
28
|
+
|
|
29
|
+
## Invariants
|
|
30
|
+
|
|
31
|
+
- Secrets and environment-specific values stay outside committed source.
|
|
32
|
+
- The application owns auth/profile and OAuth visibility policy.
|
|
33
|
+
- The installed runtime package supplies the provider implementation and its
|
|
34
|
+
npm dependency closure.
|
|
35
|
+
- No setup questionnaire or source mutation is required.
|
|
36
|
+
|
|
37
|
+
## Framework APIs
|
|
38
|
+
|
|
39
|
+
Use `@jskit-ai/auth-provider-supabase-core` for the provider and
|
|
40
|
+
`@jskit-ai/auth-core` policy contracts. Configure `config.auth` through the
|
|
41
|
+
application's ordinary server configuration.
|
|
42
|
+
|
|
43
|
+
## Example files
|
|
44
|
+
|
|
45
|
+
`example/package.json` declares the selected provider. `example/.env.example`
|
|
46
|
+
names required environment inputs without credentials. `example/config/server.js`
|
|
47
|
+
shows the app-owned profile and OAuth visibility decisions.
|
|
48
|
+
|
|
49
|
+
## Variation points
|
|
50
|
+
|
|
51
|
+
Change the OAuth provider list, default provider, profile projection mode,
|
|
52
|
+
public URL, and deployment secret source. Compose account/profile packages only
|
|
53
|
+
when the product needs their behavior.
|
|
54
|
+
|
|
55
|
+
## Verification
|
|
56
|
+
|
|
57
|
+
- Start the application with missing values and confirm concise diagnostics.
|
|
58
|
+
- Complete sign-in, callback, refresh, sign-out, and rejected-session cases.
|
|
59
|
+
- Verify allowed redirect origins and negative cross-origin behavior.
|
|
60
|
+
- Run the application auth and browser tests.
|
|
61
|
+
|
|
62
|
+
## Avoid
|
|
63
|
+
|
|
64
|
+
- committing provider keys
|
|
65
|
+
- silently selecting Supabase for the user
|
|
66
|
+
- generated configuration fragments with hidden overwrite rules
|
|
67
|
+
- setup receipts, provenance, replay logs, or questionnaire answers
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import {
|
|
2
|
+
attachDeferredAuthService,
|
|
3
|
+
createDeferredAuthService
|
|
4
|
+
} from "@jskit-ai/auth-core/server/deferredAuthService";
|
|
5
|
+
import { normalizeEmail } from "@jskit-ai/auth-core/server/utils";
|
|
6
|
+
import { defineProvider } from "@jskit-ai/kernel/shared/capabilities";
|
|
7
|
+
import { resolveAllowedOriginsFromSurfaceDefinitions } from "@jskit-ai/kernel/shared/support/returnToPath";
|
|
8
|
+
import { createService } from "../lib/service.js";
|
|
9
|
+
|
|
10
|
+
const PROFILE_MODES = Object.freeze(["provider", "users"]);
|
|
11
|
+
|
|
12
|
+
function normalizeRecord(value) {
|
|
13
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function splitCsv(value) {
|
|
17
|
+
return String(value || "").split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function oauthConfig(config) {
|
|
21
|
+
const oauth = normalizeRecord(normalizeRecord(config.auth).oauth);
|
|
22
|
+
return {
|
|
23
|
+
providers: Array.isArray(oauth.providers)
|
|
24
|
+
? oauth.providers.map((entry) => String(entry || "").trim()).filter(Boolean)
|
|
25
|
+
: splitCsv(oauth.providers),
|
|
26
|
+
defaultProvider: String(oauth.defaultProvider || "").trim()
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function providerConfig(env, config) {
|
|
31
|
+
const configuredOauth = oauthConfig(config);
|
|
32
|
+
const envProviders = splitCsv(env.AUTH_OAUTH_PROVIDERS);
|
|
33
|
+
return {
|
|
34
|
+
id: "supabase",
|
|
35
|
+
supabaseUrl: String(env.AUTH_SUPABASE_URL || env.SUPABASE_URL || "").trim(),
|
|
36
|
+
supabasePublishableKey: String(
|
|
37
|
+
env.AUTH_SUPABASE_PUBLISHABLE_KEY || env.SUPABASE_PUBLISHABLE_KEY || ""
|
|
38
|
+
).trim(),
|
|
39
|
+
jwtAudience: String(env.AUTH_JWT_AUDIENCE || "authenticated").trim(),
|
|
40
|
+
oauthProviders: envProviders.length > 0 ? envProviders : configuredOauth.providers,
|
|
41
|
+
oauthDefaultProvider: String(env.AUTH_OAUTH_DEFAULT_PROVIDER || "").trim() || configuredOauth.defaultProvider
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function profileMode(config) {
|
|
46
|
+
const mode = String(normalizeRecord(config.auth).profileMode || "provider").trim().toLowerCase();
|
|
47
|
+
if (!PROFILE_MODES.includes(mode)) {
|
|
48
|
+
throw new Error(`Unsupported config.auth.profileMode "${mode}". Supported values: ${PROFILE_MODES.join(", ")}.`);
|
|
49
|
+
}
|
|
50
|
+
return mode;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function createProviderProfileProjector() {
|
|
54
|
+
return Object.freeze({
|
|
55
|
+
async findByIdentity() { return null; },
|
|
56
|
+
async syncIdentityProfile(profileLike) {
|
|
57
|
+
const profile = normalizeRecord(profileLike);
|
|
58
|
+
const authProviderUserSid = String(profile.authProviderUserSid || "").trim();
|
|
59
|
+
const email = normalizeEmail(profile.email || "");
|
|
60
|
+
if (!authProviderUserSid || !email) {
|
|
61
|
+
throw new TypeError("Provider identity profile requires authProviderUserSid and email.");
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
id: authProviderUserSid,
|
|
65
|
+
authProvider: String(profile.authProvider || "supabase").trim() || "supabase",
|
|
66
|
+
authProviderUserSid,
|
|
67
|
+
email,
|
|
68
|
+
displayName: String(profile.displayName || email.split("@")[0] || "User").trim(),
|
|
69
|
+
profileSource: "auth-provider"
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function assertSelectedProvider(env) {
|
|
76
|
+
const selected = String(env.AUTH_PROVIDER || "").trim().toLowerCase();
|
|
77
|
+
if (selected && selected !== "supabase") {
|
|
78
|
+
throw new Error(`AUTH_PROVIDER is "${selected}", but the installed auth provider is Supabase.`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const AuthSupabaseProvider = defineProvider({
|
|
83
|
+
id: "auth.supabase",
|
|
84
|
+
requires: {
|
|
85
|
+
config: "runtime.config",
|
|
86
|
+
env: "runtime.env",
|
|
87
|
+
extensions: "auth.extensions"
|
|
88
|
+
},
|
|
89
|
+
optional: {
|
|
90
|
+
identity: "users.identity"
|
|
91
|
+
},
|
|
92
|
+
provides: {
|
|
93
|
+
authService: "auth.service"
|
|
94
|
+
},
|
|
95
|
+
setup({ env }) {
|
|
96
|
+
assertSelectedProvider(env);
|
|
97
|
+
return { authService: createDeferredAuthService() };
|
|
98
|
+
},
|
|
99
|
+
boot({ config, env, extensions, identity }, { outputs }) {
|
|
100
|
+
assertSelectedProvider(env);
|
|
101
|
+
const mode = profileMode(config);
|
|
102
|
+
if (mode === "users" && !identity) {
|
|
103
|
+
throw new Error('Supabase auth profileMode "users" requires the users.identity capability.');
|
|
104
|
+
}
|
|
105
|
+
const projector = mode === "users" ? identity.profileProjector : createProviderProfileProjector();
|
|
106
|
+
const appPublicUrl = String(env.APP_PUBLIC_URL || "").trim();
|
|
107
|
+
const baseService = createService({
|
|
108
|
+
authProvider: providerConfig(env, config),
|
|
109
|
+
appPublicUrl,
|
|
110
|
+
authAllowedReturnToOrigins: resolveAllowedOriginsFromSurfaceDefinitions(config.surfaceDefinitions || {}, {
|
|
111
|
+
seedOrigins: [appPublicUrl]
|
|
112
|
+
}),
|
|
113
|
+
nodeEnv: String(env.NODE_ENV || "development").trim() || "development",
|
|
114
|
+
userSettingsRepository: mode === "users" ? identity.repositories.userSettings : null,
|
|
115
|
+
userProfilesRepository: mode === "users" ? identity.repositories.userProfiles : null,
|
|
116
|
+
userProfileSyncService: projector,
|
|
117
|
+
profileProjectionEnabled: mode === "users",
|
|
118
|
+
devAuthBypassEnabled: env.AUTH_DEV_BYPASS_ENABLED,
|
|
119
|
+
devAuthBypassSecret: String(env.AUTH_DEV_BYPASS_SECRET || "").trim(),
|
|
120
|
+
devAuthAccessTtlSeconds: env.AUTH_DEV_ACCESS_TTL_SECONDS,
|
|
121
|
+
devAuthRefreshTtlSeconds: env.AUTH_DEV_REFRESH_TTL_SECONDS
|
|
122
|
+
});
|
|
123
|
+
attachDeferredAuthService(outputs.authService, extensions.decorateService(baseService));
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
export { AuthSupabaseProvider };
|
|
@@ -3,8 +3,7 @@ import { readFile } from "node:fs/promises";
|
|
|
3
3
|
import test from "node:test";
|
|
4
4
|
|
|
5
5
|
const EXPECTED_EXPORTS = Object.freeze({
|
|
6
|
-
"./server/providers/
|
|
7
|
-
"./server/providers/AuthProviderServiceProvider": "./src/server/providers/AuthProviderServiceProvider.js",
|
|
6
|
+
"./server/providers/AuthSupabaseProvider": "./src/server/providers/AuthSupabaseProvider.js",
|
|
8
7
|
"./server/lib/index": "./src/server/lib/index.js",
|
|
9
8
|
"./client": "./src/client/index.js"
|
|
10
9
|
});
|