@pikku/inspector 0.12.18 → 0.12.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +100 -0
- package/dist/add/add-auth.d.ts +28 -0
- package/dist/add/add-auth.js +172 -18
- package/dist/error-codes.d.ts +2 -0
- package/dist/error-codes.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/inspector.js +6 -1
- package/dist/types.d.ts +36 -0
- package/dist/utils/check-pii-output.js +26 -3
- package/dist/utils/post-process.d.ts +14 -0
- package/dist/utils/post-process.js +26 -0
- package/dist/utils/serialize-inspector-state.d.ts +2 -0
- package/dist/utils/serialize-inspector-state.js +4 -0
- package/package.json +2 -2
- package/src/add/add-auth.test.ts +308 -44
- package/src/add/add-auth.ts +223 -22
- package/src/add/pii-check.test.ts +8 -3
- package/src/error-codes.ts +4 -0
- package/src/index.ts +1 -0
- package/src/inspector.ts +6 -0
- package/src/types.ts +37 -0
- package/src/utils/check-pii-output.ts +27 -5
- package/src/utils/post-process.ts +27 -0
- package/src/utils/serialize-inspector-state.ts +6 -0
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,103 @@
|
|
|
1
|
+
## 0.12.20
|
|
2
|
+
|
|
3
|
+
### Patch Changes
|
|
4
|
+
|
|
5
|
+
- a027a8e: feat: emit auth provider + plugin metadata as `auth-meta.gen.json` for the console SSO page
|
|
6
|
+
|
|
7
|
+
The enabled social providers and Better Auth plugins are now extracted statically
|
|
8
|
+
and written to a generated `auth-meta.gen.json`, replacing the runtime
|
|
9
|
+
`setAuthRegistry`/`getAuthRegistry` approach — so the console can show them without
|
|
10
|
+
evaluating the Better Auth factory.
|
|
11
|
+
- **inspector**: the `pikkuBetterAuth` inspector now reads the `plugins` array from
|
|
12
|
+
the `betterAuth({ ... })` config and records each plugin id (the callee name of
|
|
13
|
+
each `plugins: [organization(), bearer()]` entry) on the auth definition.
|
|
14
|
+
- **cli**: `pikku auth` (and `pikku all`) emit `auth/pikku-auth-meta.gen.json` (path
|
|
15
|
+
configurable via `authMetaJsonFile`) containing `basePath`, `hasCredentials`, the
|
|
16
|
+
enabled `providers` (`id` + `displayName` + `secretId`), and the enabled `plugins`
|
|
17
|
+
(`id` + `displayName`). The previous `setAuthRegistry(...)` runtime wiring is
|
|
18
|
+
removed from the generated `auth.gen.ts`.
|
|
19
|
+
- **better-auth**: exports a `PLUGIN_REGISTRY` and `pluginDisplayName(id)` helper so
|
|
20
|
+
plugin ids resolve to human-readable names.
|
|
21
|
+
- **core**: removes the unreleased `setAuthRegistry`/`getAuthRegistry` runtime auth
|
|
22
|
+
registry (now superseded by `auth-meta.gen.json`).
|
|
23
|
+
- **addon-console**: `getAuthProviders` reads `auth-meta.gen.json` and returns the
|
|
24
|
+
configured providers, plugins, and `hasCredentials` flag.
|
|
25
|
+
- **console**: the Auth Providers (SSO) page fetches `console:getAuthProviders` and
|
|
26
|
+
marks each provider configured/unconfigured, lists email+password credentials as a
|
|
27
|
+
provider, and shows the enabled Better Auth plugins.
|
|
28
|
+
|
|
29
|
+
- a027a8e: fix: address Better Auth review findings (secret/variable batch typing, auth init, guards)
|
|
30
|
+
- **core**: `SecretService.getSecrets` / `VariablesService.getVariables` (and the
|
|
31
|
+
Local/Typed/Scoped/AWS implementations) now return `Partial<T>`, honestly
|
|
32
|
+
reflecting that missing keys are omitted at runtime rather than typing partial
|
|
33
|
+
data as fully populated. `ScopedSecretService.getSecrets` now throws on a
|
|
34
|
+
disallowed key instead of silently filtering it out.
|
|
35
|
+
- **cli**: the generated `services.auth()` thunk clears its memoised promise on
|
|
36
|
+
rejection, so a transient Better Auth/Kysely startup failure no longer
|
|
37
|
+
permanently poisons auth for the process lifetime.
|
|
38
|
+
- **inspector**: the `pikkuBetterAuth` export guard now requires an exported
|
|
39
|
+
`const` (rejects `export let`/`export var`), matching its error message.
|
|
40
|
+
- **console**: the Microsoft auth provider's `callbackId` is `microsoft` (the
|
|
41
|
+
Better Auth provider id) rather than `microsoft-entra-id`.
|
|
42
|
+
|
|
43
|
+
- a027a8e: feat(auth): migrate auth integration from Auth.js to Better Auth
|
|
44
|
+
|
|
45
|
+
The auth integration is now built on [Better Auth](https://better-auth.com)
|
|
46
|
+
and ships as a single package, `@pikku/better-auth` (replacing the former
|
|
47
|
+
`@pikku/auth-js`). There is exactly one auth package now.
|
|
48
|
+
- `pikkuBetterAuth(async ({ secrets, variables }) => betterAuth({ ... }))` is the new
|
|
49
|
+
single entry point. The CLI inspects the `betterAuth(...)` call and generates:
|
|
50
|
+
- `auth.gen.ts` — a catch-all `${basePath}{/*splat}` HTTP route per method and
|
|
51
|
+
a global `betterAuthSession({ auth })` middleware that bridges the Better
|
|
52
|
+
Auth session into the Pikku wire session.
|
|
53
|
+
- `auth-secrets.gen.ts` — `wireSecret(BETTER_AUTH_SECRET)` plus a
|
|
54
|
+
`<PROVIDER>_OAUTH` secret for each configured social provider, and
|
|
55
|
+
`wireVariable` for non-secret provider config (e.g. `MICROSOFT_TENANT_ID`,
|
|
56
|
+
`COGNITO_DOMAIN`/`REGION`/`USER_POOL_ID`).
|
|
57
|
+
- `auth.types.ts` — a typed `pikkuBetterAuth` re-export.
|
|
58
|
+
- `add-auth` (inspector) walks into the `betterAuth(...)` options to discover the
|
|
59
|
+
configured providers and required secrets/variables.
|
|
60
|
+
- The auth secret is now auto-wired by codegen from `BETTER_AUTH_SECRET` — it no
|
|
61
|
+
longer needs to be registered as a JWT signing key in `services.ts`.
|
|
62
|
+
|
|
63
|
+
CLI fix included: scaffold files generated outside `srcDirectories` (e.g. an
|
|
64
|
+
`auth.gen.ts` under a project's `pikku/` dir) are now added to the inspector's
|
|
65
|
+
wiring files, so their routes and secret metadata are picked up. The generated
|
|
66
|
+
wiring imports Pikku types via a resolved relative path instead of a hardcoded
|
|
67
|
+
`#pikku` specifier, so templates without a `#pikku` import map type-check.
|
|
68
|
+
|
|
69
|
+
- Updated dependencies [a027a8e]
|
|
70
|
+
- Updated dependencies [a027a8e]
|
|
71
|
+
- Updated dependencies [a027a8e]
|
|
72
|
+
- Updated dependencies [a027a8e]
|
|
73
|
+
- @pikku/core@0.12.32
|
|
74
|
+
|
|
75
|
+
## 0.12.19
|
|
76
|
+
|
|
77
|
+
### Patch Changes
|
|
78
|
+
|
|
79
|
+
- fe70fe0: fix(db): make classified columns usable in Kysely queries and emit real zod
|
|
80
|
+
|
|
81
|
+
Two fixes so data-classified DB columns (`@private`/`@pii`/`@secret`, default
|
|
82
|
+
`private`) are usable end-to-end instead of poisoning ordinary app code:
|
|
83
|
+
1. **Brand marker is now optional** (`{ readonly __classification__?: ... }`)
|
|
84
|
+
in both `@pikku/core` and the `pikku db migrate` schema header. A required
|
|
85
|
+
marker made a plain value (e.g. `string`) unassignable to a branded column
|
|
86
|
+
(`Private<string>`), breaking every Kysely `where`/insert/`.set()` operand —
|
|
87
|
+
any project with classified columns failed to type-check. Optional keeps the
|
|
88
|
+
brand structurally present (so the inspector's PKU910 output check still
|
|
89
|
+
detects it) while letting plain values flow IN. The inspector's level read is
|
|
90
|
+
now union-aware (`'pii' | undefined`) so pii/secret no longer silently
|
|
91
|
+
downgrade to private.
|
|
92
|
+
2. **Zod codegen resolves classified `ColumnType<>`** to proper scalars instead
|
|
93
|
+
of `z.unknown()`. `pikku db migrate` emits `<Table>Z`/`InsertZ`/`PatchZ` from
|
|
94
|
+
the Select slot, unwrapping the brand and honoring insert-optionality from the
|
|
95
|
+
Insert slot's `| undefined`. Public `Generated<T>`/bare/nested shapes are
|
|
96
|
+
unchanged.
|
|
97
|
+
|
|
98
|
+
- Updated dependencies [fe70fe0]
|
|
99
|
+
- @pikku/core@0.12.31
|
|
100
|
+
|
|
1
101
|
## 0.12.18
|
|
2
102
|
|
|
3
103
|
### Patch Changes
|
package/dist/add/add-auth.d.ts
CHANGED
|
@@ -1,2 +1,30 @@
|
|
|
1
1
|
import type { AddWiring } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The pikku function id of the single shared auth handler the CLI generates
|
|
4
|
+
* (`export const authHandler = pikkuSessionlessFunc(...)` in auth.gen.ts). An
|
|
5
|
+
* exported top-level const collapses the catch-all `/api/auth/**` route onto one
|
|
6
|
+
* worker, and the export name becomes the function id. Shared with the CLI
|
|
7
|
+
* codegen and the post-process service stamp so all three agree on the same id
|
|
8
|
+
* without the inspector having to import `@pikku/better-auth`.
|
|
9
|
+
*/
|
|
10
|
+
export declare const AUTH_HANDLER_FUNC_ID = "authHandler";
|
|
11
|
+
/**
|
|
12
|
+
* Detects `pikkuBetterAuth((services) => betterAuth({...}))` calls.
|
|
13
|
+
*
|
|
14
|
+
* `pikkuBetterAuth` is pure: it wraps a factory that returns a configured better-auth
|
|
15
|
+
* instance and has NO side effects. The user assigns it to an exported binding,
|
|
16
|
+
* e.g.
|
|
17
|
+
*
|
|
18
|
+
* export const auth = pikkuBetterAuth(async (services) => betterAuth({ ... }))
|
|
19
|
+
*
|
|
20
|
+
* The pikku CLI discovers that single export and generates a catch-all
|
|
21
|
+
* `auth.gen.ts` that wires `${basePath}/**` to one shared handler, registers the
|
|
22
|
+
* better-auth session middleware, and emits a `wireSecret` for every configured
|
|
23
|
+
* social provider — so the auth routes and secret requirements flow through
|
|
24
|
+
* normal inspection into the deploy manifest.
|
|
25
|
+
*
|
|
26
|
+
* This add-wiring records the exported binding name, source file, basePath, the
|
|
27
|
+
* `socialProviders` keys, whether email/password is enabled, and the services
|
|
28
|
+
* the factory touches. Exactly one `pikkuBetterAuth` is allowed per codebase.
|
|
29
|
+
*/
|
|
2
30
|
export declare const addAuth: AddWiring;
|
package/dist/add/add-auth.js
CHANGED
|
@@ -1,34 +1,188 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
import { ErrorCode } from '../error-codes.js';
|
|
3
|
+
import { extractServicesFromFunction } from '../utils/extract-services.js';
|
|
4
|
+
/**
|
|
5
|
+
* The pikku function id of the single shared auth handler the CLI generates
|
|
6
|
+
* (`export const authHandler = pikkuSessionlessFunc(...)` in auth.gen.ts). An
|
|
7
|
+
* exported top-level const collapses the catch-all `/api/auth/**` route onto one
|
|
8
|
+
* worker, and the export name becomes the function id. Shared with the CLI
|
|
9
|
+
* codegen and the post-process service stamp so all three agree on the same id
|
|
10
|
+
* without the inspector having to import `@pikku/better-auth`.
|
|
11
|
+
*/
|
|
12
|
+
export const AUTH_HANDLER_FUNC_ID = 'authHandler';
|
|
13
|
+
/** The default better-auth base path when `basePath` is not configured. */
|
|
14
|
+
const DEFAULT_BASE_PATH = '/api/auth';
|
|
15
|
+
/**
|
|
16
|
+
* Find the first `betterAuth({...})` call anywhere inside the `pikkuBetterAuth`
|
|
17
|
+
* factory body. Supports both `(s) => betterAuth({...})` and
|
|
18
|
+
* `(s) => { ...; return betterAuth({...}) }`.
|
|
19
|
+
*/
|
|
20
|
+
const findBetterAuthCall = (node) => {
|
|
21
|
+
let found;
|
|
22
|
+
const visit = (n) => {
|
|
23
|
+
if (found)
|
|
24
|
+
return;
|
|
25
|
+
if (ts.isCallExpression(n) &&
|
|
26
|
+
ts.isIdentifier(n.expression) &&
|
|
27
|
+
n.expression.text === 'betterAuth') {
|
|
28
|
+
found = n;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
ts.forEachChild(n, visit);
|
|
32
|
+
};
|
|
33
|
+
visit(node);
|
|
34
|
+
return found;
|
|
35
|
+
};
|
|
36
|
+
/** Read a string-literal property off an object literal, if present. */
|
|
37
|
+
const readStringProp = (obj, name) => {
|
|
38
|
+
const prop = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
|
|
39
|
+
(ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) &&
|
|
40
|
+
p.name.text === name);
|
|
41
|
+
if (prop && ts.isStringLiteral(prop.initializer))
|
|
42
|
+
return prop.initializer.text;
|
|
43
|
+
return undefined;
|
|
44
|
+
};
|
|
45
|
+
/** Find an object-literal-valued property off an object literal, if present. */
|
|
46
|
+
const readObjectProp = (obj, name) => {
|
|
47
|
+
const prop = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
|
|
48
|
+
(ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) &&
|
|
49
|
+
p.name.text === name);
|
|
50
|
+
if (prop && ts.isObjectLiteralExpression(prop.initializer))
|
|
51
|
+
return prop.initializer;
|
|
52
|
+
return undefined;
|
|
53
|
+
};
|
|
54
|
+
/** Find an array-literal-valued property off an object literal, if present. */
|
|
55
|
+
const readArrayProp = (obj, name) => {
|
|
56
|
+
const prop = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
|
|
57
|
+
(ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) &&
|
|
58
|
+
p.name.text === name);
|
|
59
|
+
if (prop && ts.isArrayLiteralExpression(prop.initializer))
|
|
60
|
+
return prop.initializer;
|
|
61
|
+
return undefined;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Read the callee name of a `plugins: [...]` entry. better-auth plugins are
|
|
65
|
+
* factory calls (`bearer()`, `twoFactor({ ... })`, `admin()`); the entry's id
|
|
66
|
+
* is the called function's name. Member-expression callees (`foo.bar()`) and
|
|
67
|
+
* non-call entries are ignored.
|
|
68
|
+
*/
|
|
69
|
+
const readPluginId = (el) => {
|
|
70
|
+
if (ts.isCallExpression(el) && ts.isIdentifier(el.expression))
|
|
71
|
+
return el.expression.text;
|
|
72
|
+
return undefined;
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Detects `pikkuBetterAuth((services) => betterAuth({...}))` calls.
|
|
76
|
+
*
|
|
77
|
+
* `pikkuBetterAuth` is pure: it wraps a factory that returns a configured better-auth
|
|
78
|
+
* instance and has NO side effects. The user assigns it to an exported binding,
|
|
79
|
+
* e.g.
|
|
80
|
+
*
|
|
81
|
+
* export const auth = pikkuBetterAuth(async (services) => betterAuth({ ... }))
|
|
82
|
+
*
|
|
83
|
+
* The pikku CLI discovers that single export and generates a catch-all
|
|
84
|
+
* `auth.gen.ts` that wires `${basePath}/**` to one shared handler, registers the
|
|
85
|
+
* better-auth session middleware, and emits a `wireSecret` for every configured
|
|
86
|
+
* social provider — so the auth routes and secret requirements flow through
|
|
87
|
+
* normal inspection into the deploy manifest.
|
|
88
|
+
*
|
|
89
|
+
* This add-wiring records the exported binding name, source file, basePath, the
|
|
90
|
+
* `socialProviders` keys, whether email/password is enabled, and the services
|
|
91
|
+
* the factory touches. Exactly one `pikkuBetterAuth` is allowed per codebase.
|
|
92
|
+
*/
|
|
3
93
|
export const addAuth = (logger, node, _checker, state) => {
|
|
4
94
|
if (!ts.isCallExpression(node))
|
|
5
95
|
return;
|
|
6
96
|
const expression = node.expression;
|
|
7
|
-
if (!ts.isIdentifier(expression) || expression.text !== '
|
|
97
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'pikkuBetterAuth')
|
|
8
98
|
return;
|
|
9
|
-
const firstArg = node.arguments[0];
|
|
10
|
-
if (!firstArg || !ts.isObjectLiteralExpression(firstArg))
|
|
11
|
-
return;
|
|
12
|
-
const providersProp = firstArg.properties.find((p) => ts.isPropertyAssignment(p) &&
|
|
13
|
-
(ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) &&
|
|
14
|
-
p.name.text === 'providers');
|
|
15
99
|
const sourceFile = node.getSourceFile().fileName;
|
|
16
|
-
|
|
17
|
-
|
|
100
|
+
// Walk up to the `export const <name> = pikkuBetterAuth(...)` binding.
|
|
101
|
+
const varDecl = node.parent;
|
|
102
|
+
if (!ts.isVariableDeclaration(varDecl) || !ts.isIdentifier(varDecl.name)) {
|
|
103
|
+
logger.critical(ErrorCode.AUTH_NOT_EXPORTED, `pikkuBetterAuth(...) must be assigned to an exported const, e.g. \`export const auth = pikkuBetterAuth((services) => betterAuth({...}))\` in ${sourceFile}`);
|
|
18
104
|
return;
|
|
19
105
|
}
|
|
20
|
-
|
|
21
|
-
|
|
106
|
+
const exportName = varDecl.name.text;
|
|
107
|
+
// VariableDeclaration -> VariableDeclarationList -> VariableStatement
|
|
108
|
+
const declList = varDecl.parent;
|
|
109
|
+
const varStatement = declList?.parent;
|
|
110
|
+
const isConst = ts.isVariableDeclarationList(declList) &&
|
|
111
|
+
(declList.flags & ts.NodeFlags.Const) !== 0;
|
|
112
|
+
const isExported = varStatement &&
|
|
113
|
+
ts.isVariableStatement(varStatement) &&
|
|
114
|
+
varStatement.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
|
|
115
|
+
if (!isExported || !isConst) {
|
|
116
|
+
logger.critical(ErrorCode.AUTH_NOT_EXPORTED, `pikkuBetterAuth(...) must be assigned to an exported const so the CLI can import it. Use \`export const ${exportName} = pikkuBetterAuth(...)\` in ${sourceFile}`);
|
|
22
117
|
return;
|
|
23
118
|
}
|
|
24
|
-
|
|
25
|
-
.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
119
|
+
if (state.auth.definition) {
|
|
120
|
+
logger.critical(ErrorCode.DUPLICATE_AUTH_DEFINITION, `Only one pikkuBetterAuth(...) is allowed per codebase. Found a second in ${sourceFile} (first: ${state.auth.definition.sourceFile}).`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// The single argument must be the factory: (services) => betterAuth({...}).
|
|
124
|
+
const factory = node.arguments[0];
|
|
125
|
+
if (!factory ||
|
|
126
|
+
(!ts.isArrowFunction(factory) && !ts.isFunctionExpression(factory))) {
|
|
127
|
+
logger.critical(ErrorCode.MISSING_NAME, `pikkuBetterAuth(...) must take a factory function returning betterAuth(...), e.g. \`pikkuBetterAuth((services) => betterAuth({...}))\` in ${sourceFile}`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
state.auth.files.add(sourceFile);
|
|
131
|
+
// Derive the services the factory touches from its destructured first param —
|
|
132
|
+
// the same convention as every other pikku wiring. A non-destructured param
|
|
133
|
+
// yields optimized:false (handler gets all singleton services), with the
|
|
134
|
+
// standard diagnostic steering the user to destructure.
|
|
135
|
+
const services = extractServicesFromFunction(factory);
|
|
136
|
+
// Find the inner betterAuth({...}) call to read providers/basePath/credentials.
|
|
137
|
+
let basePath = DEFAULT_BASE_PATH;
|
|
138
|
+
let hasCredentials = false;
|
|
139
|
+
const betterAuthCall = findBetterAuthCall(factory);
|
|
140
|
+
const config = betterAuthCall?.arguments[0];
|
|
141
|
+
if (config && ts.isObjectLiteralExpression(config)) {
|
|
142
|
+
basePath = readStringProp(config, 'basePath') ?? DEFAULT_BASE_PATH;
|
|
143
|
+
const emailAndPassword = readObjectProp(config, 'emailAndPassword');
|
|
144
|
+
if (emailAndPassword) {
|
|
145
|
+
// `emailAndPassword: { enabled: true }` — treat a present block without an
|
|
146
|
+
// explicit `enabled: false` as credentials being available.
|
|
147
|
+
const enabledProp = emailAndPassword.properties.find((p) => ts.isPropertyAssignment(p) &&
|
|
148
|
+
ts.isIdentifier(p.name) &&
|
|
149
|
+
p.name.text === 'enabled');
|
|
150
|
+
hasCredentials =
|
|
151
|
+
!enabledProp ||
|
|
152
|
+
enabledProp.initializer.kind !== ts.SyntaxKind.FalseKeyword;
|
|
29
153
|
}
|
|
30
|
-
|
|
31
|
-
|
|
154
|
+
const socialProviders = readObjectProp(config, 'socialProviders');
|
|
155
|
+
if (socialProviders) {
|
|
156
|
+
for (const prop of socialProviders.properties) {
|
|
157
|
+
const key = (ts.isPropertyAssignment(prop) ||
|
|
158
|
+
ts.isShorthandPropertyAssignment(prop)) &&
|
|
159
|
+
(ts.isIdentifier(prop.name) || ts.isStringLiteral(prop.name))
|
|
160
|
+
? prop.name.text
|
|
161
|
+
: undefined;
|
|
162
|
+
if (key && !state.auth.providers.includes(key)) {
|
|
163
|
+
state.auth.providers.push(key);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
32
166
|
}
|
|
167
|
+
const plugins = readArrayProp(config, 'plugins');
|
|
168
|
+
if (plugins) {
|
|
169
|
+
for (const el of plugins.elements) {
|
|
170
|
+
const id = readPluginId(el);
|
|
171
|
+
if (id && !state.auth.plugins.includes(id)) {
|
|
172
|
+
state.auth.plugins.push(id);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
logger.warn(`pikkuBetterAuth in ${sourceFile}: could not statically find a betterAuth({...}) call inside the factory — social provider secrets will not be auto-wired.`);
|
|
33
179
|
}
|
|
180
|
+
state.auth.definition = {
|
|
181
|
+
exportName,
|
|
182
|
+
sourceFile,
|
|
183
|
+
basePath,
|
|
184
|
+
hasCredentials,
|
|
185
|
+
plugins: [...state.auth.plugins],
|
|
186
|
+
services,
|
|
187
|
+
};
|
|
34
188
|
};
|
package/dist/error-codes.d.ts
CHANGED
|
@@ -30,6 +30,8 @@ export declare enum ErrorCode {
|
|
|
30
30
|
INLINE_SCHEMA = "PKU489",
|
|
31
31
|
FUNCTION_METADATA_NOT_FOUND = "PKU559",
|
|
32
32
|
HANDLER_NOT_RESOLVED = "PKU568",
|
|
33
|
+
DUPLICATE_AUTH_DEFINITION = "PKU581",
|
|
34
|
+
AUTH_NOT_EXPORTED = "PKU582",
|
|
33
35
|
ROUTE_PARAM_MISMATCH = "PKU571",
|
|
34
36
|
ROUTE_QUERY_MISMATCH = "PKU572",
|
|
35
37
|
AUTH_DISABLED_REQUIRES_SESSIONLESS = "PKU573",
|
package/dist/error-codes.js
CHANGED
|
@@ -34,6 +34,9 @@ export var ErrorCode;
|
|
|
34
34
|
// Function errors
|
|
35
35
|
ErrorCode["FUNCTION_METADATA_NOT_FOUND"] = "PKU559";
|
|
36
36
|
ErrorCode["HANDLER_NOT_RESOLVED"] = "PKU568";
|
|
37
|
+
// Auth errors
|
|
38
|
+
ErrorCode["DUPLICATE_AUTH_DEFINITION"] = "PKU581";
|
|
39
|
+
ErrorCode["AUTH_NOT_EXPORTED"] = "PKU582";
|
|
37
40
|
// HTTP Route errors
|
|
38
41
|
ErrorCode["ROUTE_PARAM_MISMATCH"] = "PKU571";
|
|
39
42
|
ErrorCode["ROUTE_QUERY_MISMATCH"] = "PKU572";
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type { TypesMap } from './types-map.js';
|
|
|
3
3
|
export type * from './types.js';
|
|
4
4
|
export type { FilesAndMethodsErrors } from './utils/get-files-and-methods.js';
|
|
5
5
|
export { ErrorCode } from './error-codes.js';
|
|
6
|
+
export { AUTH_HANDLER_FUNC_ID } from './add/add-auth.js';
|
|
6
7
|
export { serializeInspectorState, deserializeInspectorState, } from './utils/serialize-inspector-state.js';
|
|
7
8
|
export type { SerializableInspectorState } from './utils/serialize-inspector-state.js';
|
|
8
9
|
export { filterInspectorState } from './utils/filter-inspector-state.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { inspect, getInitialInspectorState } from './inspector.js';
|
|
2
2
|
export { ErrorCode } from './error-codes.js';
|
|
3
|
+
export { AUTH_HANDLER_FUNC_ID } from './add/add-auth.js';
|
|
3
4
|
export { serializeInspectorState, deserializeInspectorState, } from './utils/serialize-inspector-state.js';
|
|
4
5
|
export { filterInspectorState } from './utils/filter-inspector-state.js';
|
|
5
6
|
export { resolveDeployTarget } from './utils/resolve-deploy-target.js';
|
package/dist/inspector.js
CHANGED
|
@@ -4,7 +4,7 @@ import { visitSetup, visitRoutes } from './visit.js';
|
|
|
4
4
|
import { TypesMap } from './types-map.js';
|
|
5
5
|
import { getFilesAndMethods } from './utils/get-files-and-methods.js';
|
|
6
6
|
import { findCommonAncestor } from './utils/find-root-dir.js';
|
|
7
|
-
import { aggregateRequiredServices, validateAgentModels, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, computeDiagnostics, validateSchemaWiringSeparation, } from './utils/post-process.js';
|
|
7
|
+
import { aggregateRequiredServices, stampAuthHandlerServices, validateAgentModels, validateSecretOverrides, validateVariableOverrides, validateCredentialOverrides, computeResolvedIOTypes, computeMiddlewareGroupsMeta, computePermissionsGroupsMeta, computeRequiredSchemas, computeDiagnostics, validateSchemaWiringSeparation, } from './utils/post-process.js';
|
|
8
8
|
import { generateOpenAPISpec } from './utils/serialize-openapi-json.js';
|
|
9
9
|
import { pikkuState } from '@pikku/core/internal';
|
|
10
10
|
import { resolveLatestVersions } from './utils/resolve-versions.js';
|
|
@@ -117,7 +117,9 @@ export function getInitialInspectorState(rootDir) {
|
|
|
117
117
|
},
|
|
118
118
|
auth: {
|
|
119
119
|
providers: [],
|
|
120
|
+
plugins: [],
|
|
120
121
|
files: new Set(),
|
|
122
|
+
definition: null,
|
|
121
123
|
},
|
|
122
124
|
secrets: {
|
|
123
125
|
definitions: [],
|
|
@@ -254,6 +256,9 @@ export const inspect = async (logger, routeFiles, options = {}) => {
|
|
|
254
256
|
logger.debug(`Get files and methods completed in ${(performance.now() - startFilesAndMethods).toFixed(2)}ms`);
|
|
255
257
|
if (!options.setupOnly) {
|
|
256
258
|
const startAggregate = performance.now();
|
|
259
|
+
// Apply the inspected auth handler service set before aggregation so it
|
|
260
|
+
// flows into requiredServices (the generated handler's own func is opaque).
|
|
261
|
+
stampAuthHandlerServices(state);
|
|
257
262
|
aggregateRequiredServices(state);
|
|
258
263
|
logger.debug(`Aggregate required services completed in ${(performance.now() - startAggregate).toFixed(2)}ms`);
|
|
259
264
|
computeResolvedIOTypes(state);
|
package/dist/types.d.ts
CHANGED
|
@@ -246,6 +246,37 @@ export interface InspectorDiagnostic {
|
|
|
246
246
|
sourceFile: string;
|
|
247
247
|
position: number;
|
|
248
248
|
}
|
|
249
|
+
/** A single discovered `export const X = pikkuBetterAuth((services) => betterAuth({...}))`. */
|
|
250
|
+
export interface AuthDefinition {
|
|
251
|
+
/** The exported binding name the CLI imports (`export const <exportName>`). */
|
|
252
|
+
exportName: string;
|
|
253
|
+
/** Absolute path of the file declaring it. */
|
|
254
|
+
sourceFile: string;
|
|
255
|
+
/** better-auth base path (the `basePath` option, default `/api/auth`). */
|
|
256
|
+
basePath: string;
|
|
257
|
+
/** Whether email/password auth is enabled (`emailAndPassword.enabled`). Written
|
|
258
|
+
* into the generated `auth-meta.gen.json` so the console knows credentials are
|
|
259
|
+
* available alongside the OAuth providers. */
|
|
260
|
+
hasCredentials: boolean;
|
|
261
|
+
/** better-auth plugin ids used in the config's `plugins: [...]` array, read
|
|
262
|
+
* from each entry's callee name (e.g. `bearer()` → `'bearer'`). Written into
|
|
263
|
+
* `auth-meta.gen.json` so the console SSO page can show which plugins are
|
|
264
|
+
* enabled. */
|
|
265
|
+
plugins: string[];
|
|
266
|
+
/**
|
|
267
|
+
* Singleton services the generated auth handler must have available at
|
|
268
|
+
* runtime — the services the `pikkuBetterAuth` factory reaches for (either
|
|
269
|
+
* destructured from its first param, or accessed as `services.<name>` in its
|
|
270
|
+
* body). better-auth's factory typically touches `secrets` and the DB.
|
|
271
|
+
*
|
|
272
|
+
* The generated `authHandler` calls `createAuthHandler(...).func`, an opaque
|
|
273
|
+
* property access the inspector can't see through; without this stamp the
|
|
274
|
+
* deployed auth worker would instantiate none of these services and the
|
|
275
|
+
* factory would receive an undefined `kysely`. Re-derived every inspect and
|
|
276
|
+
* applied to the handler meta before service aggregation runs.
|
|
277
|
+
*/
|
|
278
|
+
services: FunctionServicesMeta;
|
|
279
|
+
}
|
|
249
280
|
export interface InspectorState {
|
|
250
281
|
rootDir: string;
|
|
251
282
|
singletonServicesTypeImportMap: PathToNameAndType;
|
|
@@ -341,7 +372,12 @@ export interface InspectorState {
|
|
|
341
372
|
};
|
|
342
373
|
auth: {
|
|
343
374
|
providers: string[];
|
|
375
|
+
plugins: string[];
|
|
344
376
|
files: Set<string>;
|
|
377
|
+
/** The single `export const X = pikkuBetterAuth({...})` discovered in the
|
|
378
|
+
* codebase, if any. The CLI generates the `/auth/*` HTTP wiring from it.
|
|
379
|
+
* More than one `pikkuBetterAuth` is a critical error. */
|
|
380
|
+
definition: AuthDefinition | null;
|
|
345
381
|
};
|
|
346
382
|
secrets: {
|
|
347
383
|
definitions: SecretDefinitions;
|
|
@@ -17,8 +17,12 @@ export function findPiiPaths(checker, type, path = '', depth = 0, seen = new Set
|
|
|
17
17
|
return [];
|
|
18
18
|
seen.add(type);
|
|
19
19
|
// ── Is this type itself branded? ─────────────────────────────────────────
|
|
20
|
-
// Private<T> = T & { readonly __classification__
|
|
21
|
-
// where one constituent has a `__classification__` property whose type is a
|
|
20
|
+
// Private<T> = T & { readonly __classification__?: 'private' } → isIntersection()
|
|
21
|
+
// where one constituent has a `__classification__` property whose type is a
|
|
22
|
+
// string literal. The marker is OPTIONAL (so plain values stay assignable to
|
|
23
|
+
// branded columns), which means its resolved type is `'private' | undefined` —
|
|
24
|
+
// a union, not a bare literal. Read the level union-aware via `literalString`,
|
|
25
|
+
// otherwise pii/secret silently downgrade to the `'private'` fallback.
|
|
22
26
|
if (type.isIntersection()) {
|
|
23
27
|
for (const t of type.types) {
|
|
24
28
|
const classificationProp = t
|
|
@@ -28,7 +32,7 @@ export function findPiiPaths(checker, type, path = '', depth = 0, seen = new Set
|
|
|
28
32
|
const decl = classificationProp.valueDeclaration ??
|
|
29
33
|
classificationProp.declarations?.[0];
|
|
30
34
|
const classification = decl
|
|
31
|
-
? (checker.getTypeOfSymbolAtLocation(classificationProp, decl)
|
|
35
|
+
? (literalString(checker.getTypeOfSymbolAtLocation(classificationProp, decl)) ?? 'private')
|
|
32
36
|
: 'private';
|
|
33
37
|
return [{ path: path || '<return value>', classification }];
|
|
34
38
|
}
|
|
@@ -71,3 +75,22 @@ export function findPiiPaths(checker, type, path = '', depth = 0, seen = new Set
|
|
|
71
75
|
}
|
|
72
76
|
return violations;
|
|
73
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Recover a string-literal value from a type that may be the literal itself or a
|
|
80
|
+
* union containing it (e.g. `'private' | undefined`, produced by the optional
|
|
81
|
+
* `__classification__?` marker). Returns undefined when no string literal is
|
|
82
|
+
* present so the caller can apply its own fallback.
|
|
83
|
+
*/
|
|
84
|
+
function literalString(type) {
|
|
85
|
+
const value = type.value;
|
|
86
|
+
if (typeof value === 'string')
|
|
87
|
+
return value;
|
|
88
|
+
if (type.isUnion()) {
|
|
89
|
+
for (const member of type.types) {
|
|
90
|
+
const found = literalString(member);
|
|
91
|
+
if (found !== undefined)
|
|
92
|
+
return found;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import type { InspectorState, InspectorLogger, InspectorOptions } from '../types.js';
|
|
2
2
|
import type { MiddlewareMetadata, PermissionMetadata } from '@pikku/core';
|
|
3
|
+
/**
|
|
4
|
+
* Stamp the inspected authorize/callbacks service set onto the generated auth
|
|
5
|
+
* handler's function meta.
|
|
6
|
+
*
|
|
7
|
+
* The CLI generates `export const authHandler = pikkuSessionlessFunc({ func:
|
|
8
|
+
* createAuthHandler(...).func })`. That `func` is an opaque property access, so
|
|
9
|
+
* normal extraction records zero services for the handler — which would leave
|
|
10
|
+
* the deployed auth worker without `kysely`/`variables`/`secrets` and break
|
|
11
|
+
* `authorize` at runtime. `add-auth` already computed the real dependency set
|
|
12
|
+
* (from the pikkuBetterAuth source) into `state.auth.definition.services`; copy it
|
|
13
|
+
* onto the handler meta. Re-derived every inspect and ordered BEFORE
|
|
14
|
+
* `aggregateRequiredServices` so it flows into `requiredServices`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function stampAuthHandlerServices(state: InspectorState | Omit<InspectorState, 'typesLookup'>): void;
|
|
3
17
|
/**
|
|
4
18
|
* Helper to extract wire-level middleware/permission names from metadata.
|
|
5
19
|
* Only extracts type:'wire' variants (individual middleware/permissions).
|
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
import { extractTypeKeys } from './type-utils.js';
|
|
2
2
|
import { ErrorCode } from '../error-codes.js';
|
|
3
|
+
import { AUTH_HANDLER_FUNC_ID } from '../add/add-auth.js';
|
|
4
|
+
/**
|
|
5
|
+
* Stamp the inspected authorize/callbacks service set onto the generated auth
|
|
6
|
+
* handler's function meta.
|
|
7
|
+
*
|
|
8
|
+
* The CLI generates `export const authHandler = pikkuSessionlessFunc({ func:
|
|
9
|
+
* createAuthHandler(...).func })`. That `func` is an opaque property access, so
|
|
10
|
+
* normal extraction records zero services for the handler — which would leave
|
|
11
|
+
* the deployed auth worker without `kysely`/`variables`/`secrets` and break
|
|
12
|
+
* `authorize` at runtime. `add-auth` already computed the real dependency set
|
|
13
|
+
* (from the pikkuBetterAuth source) into `state.auth.definition.services`; copy it
|
|
14
|
+
* onto the handler meta. Re-derived every inspect and ordered BEFORE
|
|
15
|
+
* `aggregateRequiredServices` so it flows into `requiredServices`.
|
|
16
|
+
*/
|
|
17
|
+
export function stampAuthHandlerServices(state) {
|
|
18
|
+
const definition = state.auth.definition;
|
|
19
|
+
if (!definition)
|
|
20
|
+
return;
|
|
21
|
+
const handlerMeta = state.functions.meta[AUTH_HANDLER_FUNC_ID];
|
|
22
|
+
if (!handlerMeta)
|
|
23
|
+
return;
|
|
24
|
+
handlerMeta.services = {
|
|
25
|
+
optimized: definition.services.optimized,
|
|
26
|
+
services: [...definition.services.services],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
3
29
|
/**
|
|
4
30
|
* Helper to extract wire-level middleware/permission names from metadata.
|
|
5
31
|
* Only extracts type:'wire' variants (individual middleware/permissions).
|
|
@@ -205,7 +205,9 @@ export interface SerializableInspectorState {
|
|
|
205
205
|
};
|
|
206
206
|
auth: {
|
|
207
207
|
providers: string[];
|
|
208
|
+
plugins: string[];
|
|
208
209
|
files: string[];
|
|
210
|
+
definition: InspectorState['auth']['definition'];
|
|
209
211
|
};
|
|
210
212
|
secrets: {
|
|
211
213
|
definitions: InspectorState['secrets']['definitions'];
|
|
@@ -100,7 +100,9 @@ export function serializeInspectorState(state) {
|
|
|
100
100
|
},
|
|
101
101
|
auth: {
|
|
102
102
|
providers: state.auth.providers,
|
|
103
|
+
plugins: state.auth.plugins,
|
|
103
104
|
files: Array.from(state.auth.files),
|
|
105
|
+
definition: state.auth.definition,
|
|
104
106
|
},
|
|
105
107
|
secrets: {
|
|
106
108
|
definitions: state.secrets.definitions,
|
|
@@ -252,7 +254,9 @@ export function deserializeInspectorState(data) {
|
|
|
252
254
|
},
|
|
253
255
|
auth: {
|
|
254
256
|
providers: data.auth?.providers || [],
|
|
257
|
+
plugins: data.auth?.plugins || [],
|
|
255
258
|
files: new Set(data.auth?.files || []),
|
|
259
|
+
definition: data.auth?.definition || null,
|
|
256
260
|
},
|
|
257
261
|
secrets: {
|
|
258
262
|
definitions: data.secrets?.definitions || [],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pikku/inspector",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.20",
|
|
4
4
|
"author": "yasser.fadl@gmail.com",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"type": "module",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@openapi-contrib/json-schema-to-openapi-schema": "^4.3.1",
|
|
38
|
-
"@pikku/core": "^0.12.
|
|
38
|
+
"@pikku/core": "^0.12.32",
|
|
39
39
|
"path-to-regexp": "^8.3.0",
|
|
40
40
|
"ts-json-schema-generator": "^2.5.0",
|
|
41
41
|
"tsx": "^4.21.0",
|