@igstack/app-catalog-backend-core 6.1.6-alpha-20260927042212 → 6.1.6
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/modules/auth/authRouter.d.mts +2 -1
- package/dist/modules/auth/authRouter.d.mts.map +1 -1
- package/dist/modules/auth/authRouter.mjs +2 -16
- package/dist/modules/auth/authRouter.mjs.map +1 -1
- package/dist/server/controller.d.mts +2 -1
- package/dist/server/controller.d.mts.map +1 -1
- package/package.json +4 -4
- package/src/__tests__/authProviders.test.ts +68 -41
- package/src/modules/auth/authRouter.ts +12 -40
|
@@ -2,6 +2,7 @@ import { AcTrpcContext } from "../../server/acTrpcContext.mjs";
|
|
|
2
2
|
import { BetterAuth } from "./auth.mjs";
|
|
3
3
|
import * as _trpc_server0 from "@trpc/server";
|
|
4
4
|
import { TRPCRootObject } from "@trpc/server";
|
|
5
|
+
import * as better_auth0 from "better-auth";
|
|
5
6
|
|
|
6
7
|
//#region src/modules/auth/authRouter.d.ts
|
|
7
8
|
/**
|
|
@@ -38,7 +39,7 @@ declare function createAuthRouter(t: TRPCRootObject<AcTrpcContext, {}, {}>, auth
|
|
|
38
39
|
getProviders: _trpc_server0.TRPCQueryProcedure<{
|
|
39
40
|
input: void;
|
|
40
41
|
output: {
|
|
41
|
-
providers:
|
|
42
|
+
providers: better_auth0.LiteralString[];
|
|
42
43
|
devLoginEnabled: boolean;
|
|
43
44
|
};
|
|
44
45
|
meta: {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authRouter.d.mts","names":[],"sources":["../../../src/modules/auth/authRouter.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"authRouter.d.mts","names":[],"sources":["../../../src/modules/auth/authRouter.ts"],"mappings":";;;;;;;;;;;AAUA;;iBAAgB,gBAAA,CACd,CAAA,EAAG,cAAA,CAAe,aAAA,WAClB,IAAA,GAAO,UAAA,EACP,OAAA;EAAY,eAAA;AAAA,kBAA2B,eAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA+B7B,UAAA,GAAa,UAAA,QAAkB,gBAAA"}
|
|
@@ -16,23 +16,9 @@ function createAuthRouter(t, auth, options) {
|
|
|
16
16
|
isAdmin: ctx.isAdmin
|
|
17
17
|
};
|
|
18
18
|
}),
|
|
19
|
-
getProviders: publicProcedure.query(() => {
|
|
20
|
-
const providers = [];
|
|
21
|
-
const authOptions = auth?.options;
|
|
22
|
-
if (authOptions?.socialProviders) {
|
|
23
|
-
const socialProviders = authOptions.socialProviders;
|
|
24
|
-
Object.keys(socialProviders).forEach((key) => {
|
|
25
|
-
if (socialProviders[key]) providers.push(key);
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
if (authOptions?.plugins) authOptions.plugins.forEach((plugin) => {
|
|
29
|
-
if (plugin.id !== "generic-oauth" || !plugin.options?.config) return;
|
|
30
|
-
(Array.isArray(plugin.options.config) ? plugin.options.config : [plugin.options.config]).forEach((config) => {
|
|
31
|
-
if (config.providerId) providers.push(config.providerId);
|
|
32
|
-
});
|
|
33
|
-
});
|
|
19
|
+
getProviders: publicProcedure.query(async () => {
|
|
34
20
|
return {
|
|
35
|
-
providers,
|
|
21
|
+
providers: (await auth?.$context)?.socialProviders.map((p) => p.id) ?? [],
|
|
36
22
|
devLoginEnabled: options?.devLoginEnabled ?? false
|
|
37
23
|
};
|
|
38
24
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authRouter.mjs","names":[],"sources":["../../../src/modules/auth/authRouter.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"file":"authRouter.mjs","names":[],"sources":["../../../src/modules/auth/authRouter.ts"],"sourcesContent":["import type { TRPCRootObject } from '@trpc/server'\nimport type { AcTrpcContext } from '../../server/acTrpcContext'\nimport type { BetterAuth } from './auth'\n\n/**\n * Create auth tRPC procedures\n * @param t - tRPC instance\n * @param auth - Better Auth instance (optional, for future extensions)\n * @returns tRPC router with auth procedures\n */\nexport function createAuthRouter(\n t: TRPCRootObject<AcTrpcContext, {}, {}>,\n auth?: BetterAuth,\n options?: { devLoginEnabled?: boolean },\n) {\n const router = t.router\n const publicProcedure = t.procedure\n\n return router({\n getSession: publicProcedure.query(async ({ ctx }) => {\n return {\n user: ctx.user ?? null,\n isAuthenticated: !!ctx.user,\n isAdmin: ctx.isAdmin,\n }\n }),\n getProviders: publicProcedure.query(async () => {\n // Read the providers better-auth actually resolved, not the static\n // config. The generic OAuth plugin resolves OIDC discovery once, in its\n // init, and silently skips a provider whose discovery document failed to\n // load -- for the whole life of that process. Deriving the list from the\n // static config would keep offering a sign-in button that answers 404\n // \"Provider not found\". The resolved context holds both halves: built-in\n // social providers and the generic ones that survived discovery.\n const authContext = await auth?.$context\n\n return {\n providers: authContext?.socialProviders.map((p) => p.id) ?? [],\n devLoginEnabled: options?.devLoginEnabled ?? false,\n }\n }),\n })\n}\n\nexport type AuthRouter = ReturnType<typeof createAuthRouter>\n"],"mappings":";;;;;;;AAUA,SAAgB,iBACd,GACA,MACA,SACA;CACA,MAAM,SAAS,EAAE;CACjB,MAAM,kBAAkB,EAAE;AAE1B,QAAO,OAAO;EACZ,YAAY,gBAAgB,MAAM,OAAO,EAAE,UAAU;AACnD,UAAO;IACL,MAAM,IAAI,QAAQ;IAClB,iBAAiB,CAAC,CAAC,IAAI;IACvB,SAAS,IAAI;IACd;IACD;EACF,cAAc,gBAAgB,MAAM,YAAY;AAU9C,UAAO;IACL,YAHkB,MAAM,MAAM,WAGN,gBAAgB,KAAK,MAAM,EAAE,GAAG,IAAI,EAAE;IAC9D,iBAAiB,SAAS,mBAAmB;IAC9C;IACD;EACH,CAAC"}
|
|
@@ -3,6 +3,7 @@ import { AcTrpcContext } from "./acTrpcContext.mjs";
|
|
|
3
3
|
import { BetterAuth } from "../modules/auth/auth.mjs";
|
|
4
4
|
import { CommentView } from "../modules/comments/service.mjs";
|
|
5
5
|
import * as _trpc_server0 from "@trpc/server";
|
|
6
|
+
import * as better_auth0 from "better-auth";
|
|
6
7
|
|
|
7
8
|
//#region src/server/controller.d.ts
|
|
8
9
|
/**
|
|
@@ -118,7 +119,7 @@ declare function createTrpcRouter(auth?: BetterAuth, options?: {
|
|
|
118
119
|
getProviders: _trpc_server0.TRPCQueryProcedure<{
|
|
119
120
|
input: void;
|
|
120
121
|
output: {
|
|
121
|
-
providers:
|
|
122
|
+
providers: better_auth0.LiteralString[];
|
|
122
123
|
devLoginEnabled: boolean;
|
|
123
124
|
};
|
|
124
125
|
meta: {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.mts","names":[],"sources":["../../src/server/controller.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"controller.d.mts","names":[],"sources":["../../src/server/controller.ts"],"mappings":";;;;;;;;;;;;iBAmCgB,gBAAA,CACd,IAAA,GAAO,UAAA,EACP,OAAA;EAAY,eAAA;AAAA,kBAA2B,eAAA;OADtB,aAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAwCP,UAAA,GAAa,UAAA,QAAkB,gBAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@igstack/app-catalog-backend-core",
|
|
3
|
-
"version": "6.1.6
|
|
3
|
+
"version": "6.1.6",
|
|
4
4
|
"description": "Backend core library for App Catalog",
|
|
5
5
|
"homepage": "https://github.com/lislon/app-catalog",
|
|
6
6
|
"repository": {
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"tsyringe": "^4.10.0",
|
|
45
45
|
"yaml": "^2.8.0",
|
|
46
46
|
"zod": "^4.3.5",
|
|
47
|
-
"@igstack/app-catalog-shared-core": "6.1.6
|
|
48
|
-
"@igstack/app-catalog-table-sync": "6.1.6
|
|
47
|
+
"@igstack/app-catalog-shared-core": "6.1.6",
|
|
48
|
+
"@igstack/app-catalog-table-sync": "6.1.6"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@tanstack/vite-config": "^0.4.3",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"engines": {
|
|
73
73
|
"node": ">=24"
|
|
74
74
|
},
|
|
75
|
-
"gitHead": "
|
|
75
|
+
"gitHead": "0a2ab15f93f8e6b362fa6bb09e3e06e7fed68abd",
|
|
76
76
|
"scripts": {
|
|
77
77
|
"build": "tsdown",
|
|
78
78
|
"build:lenient": "tsdown",
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { betterAuth } from 'better-auth'
|
|
2
|
+
import { memoryAdapter } from 'better-auth/adapters/memory'
|
|
1
3
|
import { genericOAuth } from 'better-auth/plugins'
|
|
2
4
|
import { describe, expect, it } from 'vitest'
|
|
3
5
|
import { createAuthRouter } from '../modules/auth/authRouter'
|
|
@@ -7,60 +9,85 @@ import { t } from '../server/trpcSetup'
|
|
|
7
9
|
|
|
8
10
|
// The sign-in page renders one button per name getProviders returns, so an
|
|
9
11
|
// upstream rename of the plugin id or of `options.config` silently leaves users
|
|
10
|
-
// with no way to sign in.
|
|
11
|
-
// a hand-written stub of
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
// with no way to sign in. A real betterAuth instance is built here on purpose --
|
|
13
|
+
// a hand-written stub of the auth context would keep passing through that
|
|
14
|
+
// rename, and would not run the plugin's own init, which is where the provider
|
|
15
|
+
// list is actually decided.
|
|
16
|
+
function auth(options: Record<string, unknown>): BetterAuth {
|
|
17
|
+
return betterAuth({
|
|
18
|
+
database: memoryAdapter({}),
|
|
19
|
+
secret: 'test-secret-that-is-long-enough',
|
|
20
|
+
baseURL: 'http://localhost',
|
|
21
|
+
// Provider skips are logged as errors; the skip is the expectation here.
|
|
22
|
+
logger: { disabled: true },
|
|
23
|
+
...options,
|
|
24
|
+
}) as BetterAuth
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function providers(instance: BetterAuth) {
|
|
28
|
+
return createAuthRouter(t, instance)
|
|
29
|
+
.createCaller({} as AcTrpcContext)
|
|
30
|
+
.getProviders()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Needs no discovery document, so it resolves without touching the network.
|
|
34
|
+
const resolvable = {
|
|
35
|
+
providerId: 'okta',
|
|
36
|
+
clientId: 'id',
|
|
37
|
+
clientSecret: 'secret',
|
|
38
|
+
authorizationUrl: 'https://example.com/oauth2/v1/authorize',
|
|
39
|
+
tokenUrl: 'https://example.com/oauth2/v1/token',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Port 1 on the loopback refuses instantly, with no DNS lookup and no reliance
|
|
43
|
+
// on the CI network -- which is exactly the unreachable-discovery-endpoint
|
|
44
|
+
// condition this guards. better-auth passes no timeout to its fetch, so a host
|
|
45
|
+
// that black-holed the connection instead would hang to vitest's default.
|
|
46
|
+
const unresolvable = {
|
|
47
|
+
providerId: 'okta',
|
|
48
|
+
clientId: 'id',
|
|
49
|
+
clientSecret: 'secret',
|
|
50
|
+
discoveryUrl: 'http://127.0.0.1:1/.well-known/openid-configuration',
|
|
17
51
|
}
|
|
18
52
|
|
|
19
53
|
describe('auth getProviders', () => {
|
|
20
54
|
it('lists a provider registered through the generic OAuth plugin', async () => {
|
|
21
|
-
const { providers: names } = await providers(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
genericOAuth({
|
|
25
|
-
config: [
|
|
26
|
-
{
|
|
27
|
-
providerId: 'okta',
|
|
28
|
-
clientId: 'id',
|
|
29
|
-
clientSecret: 'secret',
|
|
30
|
-
discoveryUrl:
|
|
31
|
-
'https://example.invalid/.well-known/openid-configuration',
|
|
32
|
-
},
|
|
33
|
-
],
|
|
34
|
-
}),
|
|
35
|
-
],
|
|
36
|
-
},
|
|
37
|
-
})
|
|
55
|
+
const { providers: names } = await providers(
|
|
56
|
+
auth({ plugins: [genericOAuth({ config: [resolvable] })] }),
|
|
57
|
+
)
|
|
38
58
|
|
|
39
59
|
expect(names).toEqual(['okta'])
|
|
40
60
|
})
|
|
41
61
|
|
|
42
62
|
it('lists built-in social providers alongside plugin ones', async () => {
|
|
43
|
-
const { providers: names } = await providers(
|
|
44
|
-
|
|
45
|
-
socialProviders: {
|
|
46
|
-
|
|
47
|
-
|
|
63
|
+
const { providers: names } = await providers(
|
|
64
|
+
auth({
|
|
65
|
+
socialProviders: { github: { clientId: 'id', clientSecret: 'secret' } },
|
|
66
|
+
plugins: [genericOAuth({ config: [resolvable] })],
|
|
67
|
+
}),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
// The plugin prepends its providers, so the order is upstream's to choose --
|
|
71
|
+
// but the set is not, and toContain alone would pass on a duplicate or an
|
|
72
|
+
// extra name.
|
|
73
|
+
expect([...names].sort()).toEqual(['github', 'okta'])
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// better-auth 1.7 resolves OIDC discovery once, in the plugin's init, and
|
|
77
|
+
// skips a provider whose discovery failed. Deriving the list from the static
|
|
78
|
+
// config instead would still offer the button, and clicking it 400s with
|
|
79
|
+
// "Provider not found" for the whole life of that pod.
|
|
80
|
+
it('omits a provider whose discovery failed, and keeps the ones that resolved', async () => {
|
|
81
|
+
const { providers: names } = await providers(
|
|
82
|
+
auth({
|
|
48
83
|
plugins: [
|
|
49
84
|
genericOAuth({
|
|
50
|
-
config: [
|
|
51
|
-
{
|
|
52
|
-
providerId: 'okta',
|
|
53
|
-
clientId: 'id',
|
|
54
|
-
clientSecret: 'secret',
|
|
55
|
-
discoveryUrl:
|
|
56
|
-
'https://example.invalid/.well-known/openid-configuration',
|
|
57
|
-
},
|
|
58
|
-
],
|
|
85
|
+
config: [unresolvable, { ...resolvable, providerId: 'other-idp' }],
|
|
59
86
|
}),
|
|
60
87
|
],
|
|
61
|
-
},
|
|
62
|
-
|
|
88
|
+
}),
|
|
89
|
+
)
|
|
63
90
|
|
|
64
|
-
expect(names).toEqual(['
|
|
91
|
+
expect(names).toEqual(['other-idp'])
|
|
65
92
|
})
|
|
66
93
|
})
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { BetterAuthPlugin } from 'better-auth'
|
|
2
1
|
import type { TRPCRootObject } from '@trpc/server'
|
|
3
2
|
import type { AcTrpcContext } from '../../server/acTrpcContext'
|
|
4
3
|
import type { BetterAuth } from './auth'
|
|
@@ -25,47 +24,20 @@ export function createAuthRouter(
|
|
|
25
24
|
isAdmin: ctx.isAdmin,
|
|
26
25
|
}
|
|
27
26
|
}),
|
|
28
|
-
getProviders: publicProcedure.query(() => {
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
getProviders: publicProcedure.query(async () => {
|
|
28
|
+
// Read the providers better-auth actually resolved, not the static
|
|
29
|
+
// config. The generic OAuth plugin resolves OIDC discovery once, in its
|
|
30
|
+
// init, and silently skips a provider whose discovery document failed to
|
|
31
|
+
// load -- for the whole life of that process. Deriving the list from the
|
|
32
|
+
// static config would keep offering a sign-in button that answers 404
|
|
33
|
+
// "Provider not found". The resolved context holds both halves: built-in
|
|
34
|
+
// social providers and the generic ones that survived discovery.
|
|
35
|
+
const authContext = await auth?.$context
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
string,
|
|
37
|
-
unknown
|
|
38
|
-
>
|
|
39
|
-
Object.keys(socialProviders).forEach((key) => {
|
|
40
|
-
if (socialProviders[key]) {
|
|
41
|
-
providers.push(key)
|
|
42
|
-
}
|
|
43
|
-
})
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Add OAuth providers from plugins (like Okta via genericOAuth). They are
|
|
47
|
-
// absent from socialProviders above: the plugin only merges them into the
|
|
48
|
-
// auth context at init time, not into the static options.
|
|
49
|
-
if (authOptions?.plugins) {
|
|
50
|
-
const plugins = authOptions.plugins
|
|
51
|
-
plugins.forEach((plugin: BetterAuthPlugin) => {
|
|
52
|
-
if (plugin.id !== 'generic-oauth' || !plugin.options?.config) {
|
|
53
|
-
return
|
|
54
|
-
}
|
|
55
|
-
const configs: { providerId?: string }[] = Array.isArray(
|
|
56
|
-
plugin.options.config,
|
|
57
|
-
)
|
|
58
|
-
? plugin.options.config
|
|
59
|
-
: [plugin.options.config]
|
|
60
|
-
configs.forEach((config) => {
|
|
61
|
-
if (config.providerId) {
|
|
62
|
-
providers.push(config.providerId)
|
|
63
|
-
}
|
|
64
|
-
})
|
|
65
|
-
})
|
|
37
|
+
return {
|
|
38
|
+
providers: authContext?.socialProviders.map((p) => p.id) ?? [],
|
|
39
|
+
devLoginEnabled: options?.devLoginEnabled ?? false,
|
|
66
40
|
}
|
|
67
|
-
|
|
68
|
-
return { providers, devLoginEnabled: options?.devLoginEnabled ?? false }
|
|
69
41
|
}),
|
|
70
42
|
})
|
|
71
43
|
}
|