cauthy-auth 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/dist/cli.js +118 -10
- package/dist/client.d.mts +11 -1
- package/dist/client.d.ts +11 -1
- package/dist/client.js +1 -1
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +1 -1
- package/dist/client.mjs.map +1 -1
- package/dist/index.js +7 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7 -5
- package/dist/index.mjs.map +1 -1
- package/dist/mcp.d.mts +36 -1
- package/dist/mcp.d.ts +36 -1
- package/dist/mcp.js +71 -5
- package/dist/mcp.js.map +1 -1
- package/dist/mcp.mjs +70 -5
- package/dist/mcp.mjs.map +1 -1
- package/dist/react.d.mts +17 -1
- package/dist/react.d.ts +17 -1
- package/dist/react.js +9 -6
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +9 -6
- package/dist/react.mjs.map +1 -1
- package/dist/svelte.js +1 -1
- package/dist/svelte.js.map +1 -1
- package/dist/svelte.mjs +1 -1
- package/dist/svelte.mjs.map +1 -1
- package/dist/vue.js +1 -1
- package/dist/vue.js.map +1 -1
- package/dist/vue.mjs +1 -1
- package/dist/vue.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,7 +101,7 @@ import 'cauthy-auth/styles.css'
|
|
|
101
101
|
|
|
102
102
|
Or draw them with your own `components` and `classNames`, and pass `unstyled`. The dashboard can start the look from your stylesheet (shadcn/ui and Tailwind, Stackmyth, or variables of your own); `npx cauthy-auth theme --css src/app/globals.css` reads it the same way from a terminal.
|
|
103
103
|
|
|
104
|
-
Connect your application to Cauthy in one step, from Cauthy's dashboard ("Connect a System") or from a terminal. In the application's folder, `init` reads its framework (Next.js, TanStack Start, SvelteKit, Express, Hono, Fastify), makes the client, writes every variable to its env file, `CAUTHY_SECRET` included, and lays down the route and a sign-in page, never over a file that is there:
|
|
104
|
+
Connect your application to Cauthy in one step, from Cauthy's dashboard ("Connect a System") or from a terminal. In the application's folder, `init` reads its framework (Next.js, TanStack Start, React Router or Remix, SvelteKit, Vite + React, Express, Hono, Fastify), makes the client, writes every variable to its env file, `CAUTHY_SECRET` included, and lays down the route and a sign-in page, never over a file that is there:
|
|
105
105
|
|
|
106
106
|
```bash
|
|
107
107
|
npx cauthy-auth init
|
|
@@ -131,6 +131,13 @@ const auth = mcpAuth({ resource: 'https://mcp.example.com/mcp', scopes: ['books:
|
|
|
131
131
|
app.use(auth.middleware()) // or auth.protect(handler) for Request/Response
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
+
When that server calls another of your servers for the person, it trades the agent's token for one for that server (token exchange, RFC 8693). Delegate the call and create the server's credentials in the dashboard first:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
const auth = mcpAuth({ resource, clientId: process.env.CAUTHY_MCP_CLIENT_ID, clientSecret: process.env.CAUTHY_MCP_CLIENT_SECRET })
|
|
138
|
+
const { accessToken } = await auth.exchange(req.agent, 'https://api.example.com', ['orders:read'])
|
|
139
|
+
```
|
|
140
|
+
|
|
134
141
|
The full guide is in Cauthy's documentation, under "Auth SDK".
|
|
135
142
|
|
|
136
143
|
## License
|
package/dist/cli.js
CHANGED
|
@@ -57,33 +57,41 @@ var import_node_path = require("path");
|
|
|
57
57
|
var FRAMEWORK_NAMES = {
|
|
58
58
|
next: "Next.js",
|
|
59
59
|
"tanstack-start": "TanStack Start",
|
|
60
|
+
"react-router": "React Router",
|
|
60
61
|
sveltekit: "SvelteKit",
|
|
61
62
|
express: "Express",
|
|
62
63
|
hono: "Hono",
|
|
63
|
-
fastify: "Fastify"
|
|
64
|
+
fastify: "Fastify",
|
|
65
|
+
"vite-react": "Vite + React"
|
|
64
66
|
};
|
|
65
67
|
function detect(pkg, has) {
|
|
66
68
|
const deps = { ...pkg.devDependencies, ...pkg.dependencies };
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
+
const remix = Boolean(deps["@remix-run/react"] || deps["@remix-run/node"]);
|
|
70
|
+
const framework = deps.next ? "next" : deps["@tanstack/react-start"] ? "tanstack-start" : deps["@react-router/dev"] || remix ? "react-router" : deps["@sveltejs/kit"] ? "sveltekit" : deps.vite && deps.react && !deps.express && !deps.fastify ? "vite-react" : deps.hono ? "hono" : deps.fastify ? "fastify" : deps.express ? "express" : null;
|
|
71
|
+
const port = framework === "sveltekit" || framework === "vite-react" || framework === "react-router" && !(remix && !deps.vite) ? 5173 : 3e3;
|
|
69
72
|
let root = has("src") ? "src" : "";
|
|
70
73
|
let nextPagesOnly = false;
|
|
71
74
|
if (framework === "next") {
|
|
72
75
|
root = has("src/app") ? "src" : has("app") ? "" : has("src/pages") ? "src" : "";
|
|
73
76
|
nextPagesOnly = !has("src/app") && !has("app");
|
|
74
77
|
}
|
|
75
|
-
if (framework === "tanstack-start" || framework === "sveltekit") root = "src";
|
|
78
|
+
if (framework === "tanstack-start" || framework === "sveltekit" || framework === "vite-react") root = "src";
|
|
79
|
+
if (framework === "react-router") root = "app";
|
|
76
80
|
const major = /(\d+)/.exec(deps.next ?? "");
|
|
81
|
+
const needs = ["cauthy-auth", ...framework === "vite-react" ? ["hono", "@hono/node-server"] : []];
|
|
77
82
|
return {
|
|
78
83
|
framework,
|
|
79
84
|
...nextPagesOnly ? { nextPagesOnly } : {},
|
|
80
85
|
root,
|
|
86
|
+
...framework === "react-router" && remix ? { remix } : {},
|
|
87
|
+
...framework === "react-router" ? { fileRoutes: remix || Boolean(deps["@react-router/fs-routes"]) } : {},
|
|
81
88
|
typescript: has("tsconfig.json") || Boolean(deps.typescript),
|
|
82
89
|
esm: pkg.type === "module",
|
|
83
90
|
...framework === "next" && major ? { nextMajor: Number(major[1]) } : {},
|
|
84
91
|
devUrl: `http://localhost:${port}`,
|
|
85
92
|
system: systemName(pkg.name),
|
|
86
93
|
installed: Boolean(deps["cauthy-auth"]),
|
|
94
|
+
toInstall: needs.filter((name) => !deps[name]),
|
|
87
95
|
packageManager: has("pnpm-lock.yaml") ? "pnpm" : has("yarn.lock") ? "yarn" : has("bun.lockb") || has("bun.lock") ? "bun" : "npm"
|
|
88
96
|
};
|
|
89
97
|
}
|
|
@@ -100,9 +108,9 @@ function importPath(from, to) {
|
|
|
100
108
|
function scaffold(d) {
|
|
101
109
|
const ext = d.typescript ? "ts" : "js";
|
|
102
110
|
const jsx = d.typescript ? "tsx" : "jsx";
|
|
103
|
-
const install = `${d.packageManager === "npm" ? "npm install" : `${d.packageManager} add`}
|
|
111
|
+
const install = `${d.packageManager === "npm" ? "npm install" : `${d.packageManager} add`} ${d.toInstall.join(" ")}`;
|
|
104
112
|
const dev = `${d.packageManager === "npm" ? "npm run" : d.packageManager} dev`;
|
|
105
|
-
const steps = d.
|
|
113
|
+
const steps = d.toInstall.length === 0 ? [] : [`Install the ${d.toInstall.length === 1 ? "package" : "packages"}: ${install}`];
|
|
106
114
|
const files = [];
|
|
107
115
|
const signIn = `${d.devUrl}/sign-in`;
|
|
108
116
|
const hosted = `${d.devUrl}/api/auth/login`;
|
|
@@ -159,6 +167,49 @@ export const Route = createFileRoute('/sign-in')({ component: SignInPage })
|
|
|
159
167
|
steps.push(`Start the application: ${dev}`, `Open ${signIn}`);
|
|
160
168
|
return { files, steps, envFile: ".env", open: signIn };
|
|
161
169
|
}
|
|
170
|
+
case "react-router": {
|
|
171
|
+
const authFile = join(d.root, `auth.server.${ext}`);
|
|
172
|
+
files.push({ path: authFile, content: PLAIN_AUTH });
|
|
173
|
+
const route = join(d.root, "routes", `api.auth.$.${ext}`);
|
|
174
|
+
const param = d.typescript ? "{ request }: { request: Request }" : "{ request }";
|
|
175
|
+
files.push({ path: route, content: `// Serves cauthy-auth: sign-in, return, session, sign-out.
|
|
176
|
+
import { auth } from '${importPath(route, authFile)}'
|
|
177
|
+
|
|
178
|
+
export const loader = (${param}) => auth.handler(request)
|
|
179
|
+
export const action = (${param}) => auth.handler(request)
|
|
180
|
+
` });
|
|
181
|
+
const page = join(d.root, "routes", `sign-in.${jsx}`);
|
|
182
|
+
files.push({ path: page, content: reactSignInPage("", "export default function SignInPage") });
|
|
183
|
+
if (!d.fileRoutes) {
|
|
184
|
+
const rel = (p) => import_node_path.posix.relative(d.root, p);
|
|
185
|
+
steps.push(`Add the two routes to ${join(d.root, `routes.${ext}`)}:
|
|
186
|
+
|
|
187
|
+
route('api/auth/*', '${rel(route)}'),
|
|
188
|
+
route('sign-in', '${rel(page)}'),`);
|
|
189
|
+
}
|
|
190
|
+
steps.push(`Start the application: ${dev}`, `Open ${signIn}`);
|
|
191
|
+
return { files, steps, envFile: ".env", open: signIn };
|
|
192
|
+
}
|
|
193
|
+
case "vite-react": {
|
|
194
|
+
const app = join("server", `app.${ext}`);
|
|
195
|
+
files.push({ path: join("server", `auth.${ext}`), content: PLAIN_AUTH });
|
|
196
|
+
files.push({ path: app, content: viteApp(d.typescript) });
|
|
197
|
+
files.push({ path: join("server", `index.${ext}`), content: VITE_SERVER });
|
|
198
|
+
files.push({ path: join("server", `vite.${ext}`), content: viteDev(d.typescript, `/${app}`) });
|
|
199
|
+
const page = join(d.root, `SignInPage.${jsx}`);
|
|
200
|
+
files.push({ path: page, content: reactSignInPage("", "export function SignInPage") });
|
|
201
|
+
const run = d.typescript ? "npx tsx server/index.ts" : "node server/index.js";
|
|
202
|
+
steps.push(
|
|
203
|
+
`Add the plugin that serves /api while developing to vite.config.${ext}:
|
|
204
|
+
|
|
205
|
+
import { cauthyDev } from './server/vite'
|
|
206
|
+
plugins: [react(), cauthyDev()]`,
|
|
207
|
+
`Start the application: ${dev}`,
|
|
208
|
+
`Open ${hosted}: Cauthy's sign-in page, and back. For a screen of your own, show <SignInPage /> from ${page} at /sign-in.`,
|
|
209
|
+
`In production, build the pages (vite build) and run ${run}: it serves them and /api.`
|
|
210
|
+
);
|
|
211
|
+
return { files, steps, envFile: ".env", open: hosted };
|
|
212
|
+
}
|
|
162
213
|
case "sveltekit": {
|
|
163
214
|
const authFile = join(d.root, "lib/server", `auth.${ext}`);
|
|
164
215
|
files.push({ path: authFile, content: PLAIN_AUTH });
|
|
@@ -231,6 +282,62 @@ const { createCauthy } = require('cauthy-auth')
|
|
|
231
282
|
|
|
232
283
|
exports.auth = createCauthy()
|
|
233
284
|
`;
|
|
285
|
+
function viteApp(ts) {
|
|
286
|
+
return `// The application's server side: cauthy-auth's routes under /api/auth, and
|
|
287
|
+
// c.get('session') on every other /api request. While developing it runs
|
|
288
|
+
// inside Vite (server/vite); in production server/index serves it with the
|
|
289
|
+
// built pages.
|
|
290
|
+
import { Hono } from 'hono'
|
|
291
|
+
import { cauthyHono, requireSession${ts ? ", type CauthyVariables" : ""} } from 'cauthy-auth/hono'
|
|
292
|
+
import { auth } from './auth.js'
|
|
293
|
+
|
|
294
|
+
export const app = new Hono${ts ? "<{ Variables: CauthyVariables }>" : ""}()
|
|
295
|
+
app.use('/api/*', cauthyHono(auth))
|
|
296
|
+
|
|
297
|
+
// An API only a signed-in person may call: a 401 for anybody else.
|
|
298
|
+
app.get('/api/me', requireSession, (c) => c.json(c.get('session')${ts ? "!" : ""}.user))
|
|
299
|
+
`;
|
|
300
|
+
}
|
|
301
|
+
var VITE_SERVER = `// The application in production: the pages Vite built, and the API.
|
|
302
|
+
import { serve } from '@hono/node-server'
|
|
303
|
+
import { serveStatic } from '@hono/node-server/serve-static'
|
|
304
|
+
import { app } from './app.js'
|
|
305
|
+
|
|
306
|
+
app.use('/*', serveStatic({ root: './dist' }))
|
|
307
|
+
// Any other path is a page of the single-page app.
|
|
308
|
+
app.get('*', serveStatic({ path: './dist/index.html' }))
|
|
309
|
+
|
|
310
|
+
const port = Number(process.env.PORT ?? 3000)
|
|
311
|
+
serve({ fetch: app.fetch, port }, () => console.log(\`http://localhost:\${port}\`))
|
|
312
|
+
`;
|
|
313
|
+
function viteDev(ts, app) {
|
|
314
|
+
return `// While developing, Vite serves the pages and hands /api to the server's
|
|
315
|
+
// app, on the same address: no proxy, and Cauthy sends people back to the
|
|
316
|
+
// address the browser is on.
|
|
317
|
+
//
|
|
318
|
+
// import { cauthyDev } from './server/vite'
|
|
319
|
+
// plugins: [react(), cauthyDev()]
|
|
320
|
+
import { getRequestListener } from '@hono/node-server'
|
|
321
|
+
import { loadEnv${ts ? ", type Plugin" : ""} } from 'vite'
|
|
322
|
+
|
|
323
|
+
export function cauthyDev()${ts ? ": Plugin" : ""} {
|
|
324
|
+
return {
|
|
325
|
+
name: 'cauthy-dev',
|
|
326
|
+
configureServer(server) {
|
|
327
|
+
// The variables in the env file, which createCauthy() reads from the environment.
|
|
328
|
+
Object.assign(process.env, loadEnv(server.config.mode, server.config.envDir || server.config.root, 'CAUTHY_'))
|
|
329
|
+
server.middlewares.use((req, res, next) => {
|
|
330
|
+
if (!req.url?.startsWith('/api/')) return next()
|
|
331
|
+
// Loaded through Vite: a change to the server's files applies on the next request.
|
|
332
|
+
server.ssrLoadModule('${app}')
|
|
333
|
+
.then(({ app }) => getRequestListener(app.fetch)(req, res))
|
|
334
|
+
.catch(next)
|
|
335
|
+
})
|
|
336
|
+
},
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
`;
|
|
340
|
+
}
|
|
234
341
|
function reactSignInPage(head, declaration, tail = "") {
|
|
235
342
|
return `${head}// The application's own sign-in page. <SignIn /> is the ready-made form: the
|
|
236
343
|
// workspace's providers, passkeys, the second step and a sign-in link, drawn
|
|
@@ -912,9 +1019,10 @@ var INIT_HELP = `cauthy-auth init [options]
|
|
|
912
1019
|
|
|
913
1020
|
Connects an application to Cauthy and leaves signing in working. Run it in
|
|
914
1021
|
the application's folder: it reads which framework the application is made
|
|
915
|
-
with (Next.js, TanStack Start,
|
|
916
|
-
client in Cauthy, writes the variables to
|
|
917
|
-
route and a sign-in page. A file that is
|
|
1022
|
+
with (Next.js, TanStack Start, React Router or Remix, SvelteKit, Vite + React,
|
|
1023
|
+
Express, Hono, Fastify), makes its client in Cauthy, writes the variables to
|
|
1024
|
+
its env file, and lays down the route and a sign-in page. A file that is
|
|
1025
|
+
already there is never written over.
|
|
918
1026
|
|
|
919
1027
|
--system <name> The system's name: lowercase letters, digits, - or _.
|
|
920
1028
|
Default: the package's name.
|
|
@@ -976,7 +1084,7 @@ ${INIT_HELP}`);
|
|
|
976
1084
|
const system = given["--system"] ?? found?.system;
|
|
977
1085
|
const appUrl = given["--app-url"] ?? found?.devUrl;
|
|
978
1086
|
if (!system || !appUrl) fail("no package.json here: give --system and --app-url\n\n" + INIT_HELP);
|
|
979
|
-
if (found?.framework) process.stdout.write(`Found ${FRAMEWORK_NAMES[found.framework]}.
|
|
1087
|
+
if (found?.framework) process.stdout.write(`Found ${found.remix ? "Remix" : FRAMEWORK_NAMES[found.framework]}.
|
|
980
1088
|
`);
|
|
981
1089
|
const target = given["--write"] ?? plan?.envFile;
|
|
982
1090
|
let had = "";
|
package/dist/client.d.mts
CHANGED
|
@@ -319,10 +319,19 @@ interface AuthClient {
|
|
|
319
319
|
sentTo: string;
|
|
320
320
|
channel: string;
|
|
321
321
|
}>>;
|
|
322
|
-
/**
|
|
322
|
+
/**
|
|
323
|
+
* Mails a link (and a code) that signs the person in. The answer is the
|
|
324
|
+
* same whether or not the address has an account. `create`: an address
|
|
325
|
+
* with no account gets one when the link or code is used, if the
|
|
326
|
+
* workspace accepts new accounts — one screen signs people in and up.
|
|
327
|
+
* `returnTo` (a path of this site) travels in the link: the page it opens
|
|
328
|
+
* gets it as `?return_to=`, and `emailCode` answers it.
|
|
329
|
+
*/
|
|
323
330
|
magicLink(form: {
|
|
324
331
|
email: string;
|
|
325
332
|
captchaToken?: string;
|
|
333
|
+
create?: boolean;
|
|
334
|
+
returnTo?: string;
|
|
326
335
|
}): Promise<AuthResult<'sent'>>;
|
|
327
336
|
/** Signs in with the token the mailed link opened the page with (`ml_token`). */
|
|
328
337
|
magicLinkVerify(form: {
|
|
@@ -335,6 +344,7 @@ interface AuthClient {
|
|
|
335
344
|
code: string;
|
|
336
345
|
}): Promise<AuthResult<'signed_in', {
|
|
337
346
|
session: PublicSession;
|
|
347
|
+
returnTo?: string;
|
|
338
348
|
}>>;
|
|
339
349
|
/**
|
|
340
350
|
* Signs in with a passkey, on this page: the browser asks the person for
|
package/dist/client.d.ts
CHANGED
|
@@ -319,10 +319,19 @@ interface AuthClient {
|
|
|
319
319
|
sentTo: string;
|
|
320
320
|
channel: string;
|
|
321
321
|
}>>;
|
|
322
|
-
/**
|
|
322
|
+
/**
|
|
323
|
+
* Mails a link (and a code) that signs the person in. The answer is the
|
|
324
|
+
* same whether or not the address has an account. `create`: an address
|
|
325
|
+
* with no account gets one when the link or code is used, if the
|
|
326
|
+
* workspace accepts new accounts — one screen signs people in and up.
|
|
327
|
+
* `returnTo` (a path of this site) travels in the link: the page it opens
|
|
328
|
+
* gets it as `?return_to=`, and `emailCode` answers it.
|
|
329
|
+
*/
|
|
323
330
|
magicLink(form: {
|
|
324
331
|
email: string;
|
|
325
332
|
captchaToken?: string;
|
|
333
|
+
create?: boolean;
|
|
334
|
+
returnTo?: string;
|
|
326
335
|
}): Promise<AuthResult<'sent'>>;
|
|
327
336
|
/** Signs in with the token the mailed link opened the page with (`ml_token`). */
|
|
328
337
|
magicLinkVerify(form: {
|
|
@@ -335,6 +344,7 @@ interface AuthClient {
|
|
|
335
344
|
code: string;
|
|
336
345
|
}): Promise<AuthResult<'signed_in', {
|
|
337
346
|
session: PublicSession;
|
|
347
|
+
returnTo?: string;
|
|
338
348
|
}>>;
|
|
339
349
|
/**
|
|
340
350
|
* Signs in with a passkey, on this page: the browser asks the person for
|
package/dist/client.js
CHANGED
|
@@ -379,7 +379,7 @@ function createAuthClient(options = {}) {
|
|
|
379
379
|
mfa: (f) => post("/sign-in/mfa", { code: f.code }, checkCode(f.code)),
|
|
380
380
|
mfaMethods: () => post("/sign-in/mfa/methods", {}, null),
|
|
381
381
|
mfaSendCode: () => post("/sign-in/mfa/send", {}, null),
|
|
382
|
-
magicLink: (f) => post("/sign-in/magic-link", { email: f.email }, checkEmail(f.email), f.captchaToken),
|
|
382
|
+
magicLink: (f) => post("/sign-in/magic-link", { email: f.email, ...f.create ? { create: true } : {}, ...f.returnTo ? { returnTo: f.returnTo } : {} }, checkEmail(f.email), f.captchaToken),
|
|
383
383
|
magicLinkVerify: (f) => post("/sign-in/magic-link/verify", { token: f.token }, null),
|
|
384
384
|
emailCode: (f) => post("/sign-in/email-code", { code: f.code }, checkCode(f.code)),
|
|
385
385
|
passkeyUrl,
|