cauthy-auth 0.10.0 → 0.12.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 +3 -2
- package/dist/cli.js +299 -20
- package/dist/client.d.mts +88 -5
- package/dist/client.d.ts +88 -5
- package/dist/client.js +202 -11
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +200 -11
- package/dist/client.mjs.map +1 -1
- package/dist/index.js +128 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +128 -5
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +110 -5
- package/dist/react.d.ts +110 -5
- package/dist/react.js +338 -20
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +335 -20
- package/dist/react.mjs.map +1 -1
- package/dist/svelte.d.mts +1 -1
- package/dist/svelte.d.ts +1 -1
- package/dist/svelte.js +198 -11
- package/dist/svelte.js.map +1 -1
- package/dist/svelte.mjs +198 -11
- package/dist/svelte.mjs.map +1 -1
- package/dist/vue.d.mts +1 -1
- package/dist/vue.d.ts +1 -1
- package/dist/vue.js +198 -11
- package/dist/vue.js.map +1 -1
- package/dist/vue.mjs +198 -11
- package/dist/vue.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,10 +101,11 @@ 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.
|
|
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:
|
|
105
105
|
|
|
106
106
|
```bash
|
|
107
|
-
npx cauthy-auth init
|
|
107
|
+
npx cauthy-auth init
|
|
108
|
+
npx cauthy-auth init --system ledger --app-url https://app.example.com # production
|
|
108
109
|
```
|
|
109
110
|
|
|
110
111
|
The same client is your application's own identity: it manages its organizations with no person's token.
|
package/dist/cli.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
5
|
var import_node_fs = require("fs");
|
|
6
|
+
var import_node_path2 = require("path");
|
|
6
7
|
|
|
7
8
|
// src/connect.ts
|
|
8
9
|
function sessionSecret() {
|
|
@@ -51,6 +52,206 @@ function envFile(env2) {
|
|
|
51
52
|
return Object.entries(env2).map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
// src/scaffold.ts
|
|
56
|
+
var import_node_path = require("path");
|
|
57
|
+
var FRAMEWORK_NAMES = {
|
|
58
|
+
next: "Next.js",
|
|
59
|
+
"tanstack-start": "TanStack Start",
|
|
60
|
+
sveltekit: "SvelteKit",
|
|
61
|
+
express: "Express",
|
|
62
|
+
hono: "Hono",
|
|
63
|
+
fastify: "Fastify"
|
|
64
|
+
};
|
|
65
|
+
function detect(pkg, has) {
|
|
66
|
+
const deps = { ...pkg.devDependencies, ...pkg.dependencies };
|
|
67
|
+
const framework = deps.next ? "next" : deps["@tanstack/react-start"] ? "tanstack-start" : deps["@sveltejs/kit"] ? "sveltekit" : deps.hono ? "hono" : deps.fastify ? "fastify" : deps.express ? "express" : null;
|
|
68
|
+
const port = framework === "sveltekit" ? 5173 : 3e3;
|
|
69
|
+
let root = has("src") ? "src" : "";
|
|
70
|
+
let nextPagesOnly = false;
|
|
71
|
+
if (framework === "next") {
|
|
72
|
+
root = has("src/app") ? "src" : has("app") ? "" : has("src/pages") ? "src" : "";
|
|
73
|
+
nextPagesOnly = !has("src/app") && !has("app");
|
|
74
|
+
}
|
|
75
|
+
if (framework === "tanstack-start" || framework === "sveltekit") root = "src";
|
|
76
|
+
const major = /(\d+)/.exec(deps.next ?? "");
|
|
77
|
+
return {
|
|
78
|
+
framework,
|
|
79
|
+
...nextPagesOnly ? { nextPagesOnly } : {},
|
|
80
|
+
root,
|
|
81
|
+
typescript: has("tsconfig.json") || Boolean(deps.typescript),
|
|
82
|
+
esm: pkg.type === "module",
|
|
83
|
+
...framework === "next" && major ? { nextMajor: Number(major[1]) } : {},
|
|
84
|
+
devUrl: `http://localhost:${port}`,
|
|
85
|
+
system: systemName(pkg.name),
|
|
86
|
+
installed: Boolean(deps["cauthy-auth"]),
|
|
87
|
+
packageManager: has("pnpm-lock.yaml") ? "pnpm" : has("yarn.lock") ? "yarn" : has("bun.lockb") || has("bun.lock") ? "bun" : "npm"
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function systemName(pkgName) {
|
|
91
|
+
const bare = (pkgName ?? "").replace(/^@[^/]+\//, "");
|
|
92
|
+
const name = bare.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^[-_]+|[-_]+$/g, "");
|
|
93
|
+
return name || "app";
|
|
94
|
+
}
|
|
95
|
+
var join = (...parts) => import_node_path.posix.join(...parts.filter(Boolean));
|
|
96
|
+
function importPath(from, to) {
|
|
97
|
+
const rel = import_node_path.posix.relative(import_node_path.posix.dirname(from), to).replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
98
|
+
return rel.startsWith(".") ? rel : `./${rel}`;
|
|
99
|
+
}
|
|
100
|
+
function scaffold(d) {
|
|
101
|
+
const ext = d.typescript ? "ts" : "js";
|
|
102
|
+
const jsx = d.typescript ? "tsx" : "jsx";
|
|
103
|
+
const install = `${d.packageManager === "npm" ? "npm install" : `${d.packageManager} add`} cauthy-auth`;
|
|
104
|
+
const dev = `${d.packageManager === "npm" ? "npm run" : d.packageManager} dev`;
|
|
105
|
+
const steps = d.installed ? [] : [`Install the package: ${install}`];
|
|
106
|
+
const files = [];
|
|
107
|
+
const signIn = `${d.devUrl}/sign-in`;
|
|
108
|
+
const hosted = `${d.devUrl}/api/auth/login`;
|
|
109
|
+
switch (d.framework) {
|
|
110
|
+
case "next": {
|
|
111
|
+
const authFile = join(d.root, "lib", `auth.${ext}`);
|
|
112
|
+
files.push({ path: authFile, content: NEXT_AUTH });
|
|
113
|
+
if (d.nextPagesOnly) {
|
|
114
|
+
steps.push('The pages router is not written for you: serve auth.handler(request) under /api/auth/* (see the Auth SDK guide, "Mount the route").');
|
|
115
|
+
return { files, steps, envFile: ".env.local", open: hosted };
|
|
116
|
+
}
|
|
117
|
+
const route = join(d.root, "app/api/auth/[...cauthy]", `route.${ext}`);
|
|
118
|
+
files.push({ path: route, content: `// Serves cauthy-auth: sign-in, return, session, sign-out.
|
|
119
|
+
import { cauthy } from '${importPath(route, authFile)}'
|
|
120
|
+
|
|
121
|
+
export const { GET, POST } = cauthy
|
|
122
|
+
` });
|
|
123
|
+
const before = join(d.root, `${(d.nextMajor ?? 15) >= 16 ? "proxy" : "middleware"}.${ext}`);
|
|
124
|
+
files.push({ path: before, content: `// Keeps the session renewed. To send anybody not signed in away from some
|
|
125
|
+
// pages: cauthy.middleware({ protect: (path) => path.startsWith('/app') }).
|
|
126
|
+
import { cauthy } from '${importPath(before, authFile)}'
|
|
127
|
+
|
|
128
|
+
export default cauthy.middleware()
|
|
129
|
+
|
|
130
|
+
export const config = { matcher: ['/((?!_next/|favicon.ico).*)'] }
|
|
131
|
+
` });
|
|
132
|
+
files.push({ path: join(d.root, "app/sign-in", `page.${jsx}`), content: reactSignInPage(`'use client'
|
|
133
|
+
|
|
134
|
+
`, "export default function SignInPage") });
|
|
135
|
+
steps.push(`Start the application: ${dev}`, `Open ${signIn}`);
|
|
136
|
+
return { files, steps, envFile: ".env.local", open: signIn };
|
|
137
|
+
}
|
|
138
|
+
case "tanstack-start": {
|
|
139
|
+
const authFile = join(d.root, `auth.${ext}`);
|
|
140
|
+
files.push({ path: authFile, content: PLAIN_AUTH });
|
|
141
|
+
const route = join(d.root, "routes/api/auth", `$.${ext}`);
|
|
142
|
+
files.push({ path: route, content: `// Serves cauthy-auth: sign-in, return, session, sign-out.
|
|
143
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
144
|
+
import { auth } from '${importPath(route, authFile)}'
|
|
145
|
+
|
|
146
|
+
export const Route = createFileRoute('/api/auth/$')({
|
|
147
|
+
server: {
|
|
148
|
+
handlers: {
|
|
149
|
+
GET: ({ request }) => auth.handler(request),
|
|
150
|
+
POST: ({ request }) => auth.handler(request),
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
})
|
|
154
|
+
` });
|
|
155
|
+
files.push({ path: join(d.root, "routes", `sign-in.${jsx}`), content: reactSignInPage(`import { createFileRoute } from '@tanstack/react-router'
|
|
156
|
+
`, "function SignInPage", `
|
|
157
|
+
export const Route = createFileRoute('/sign-in')({ component: SignInPage })
|
|
158
|
+
`) });
|
|
159
|
+
steps.push(`Start the application: ${dev}`, `Open ${signIn}`);
|
|
160
|
+
return { files, steps, envFile: ".env", open: signIn };
|
|
161
|
+
}
|
|
162
|
+
case "sveltekit": {
|
|
163
|
+
const authFile = join(d.root, "lib/server", `auth.${ext}`);
|
|
164
|
+
files.push({ path: authFile, content: PLAIN_AUTH });
|
|
165
|
+
const route = join(d.root, "routes/api/auth/[...cauthy]", `+server.${ext}`);
|
|
166
|
+
const param = d.typescript ? "{ request }: { request: Request }" : "{ request }";
|
|
167
|
+
files.push({ path: route, content: `// Serves cauthy-auth: sign-in, return, session, sign-out.
|
|
168
|
+
import { auth } from '$lib/server/auth'
|
|
169
|
+
|
|
170
|
+
export const GET = (${param}) => auth.handler(request)
|
|
171
|
+
export const POST = (${param}) => auth.handler(request)
|
|
172
|
+
` });
|
|
173
|
+
steps.push(`Start the application: ${dev}`, `Open ${hosted}: Cauthy's sign-in page, and back. For screens of your own, see createCauthySession in cauthy-auth/svelte.`);
|
|
174
|
+
return { files, steps, envFile: ".env", open: hosted };
|
|
175
|
+
}
|
|
176
|
+
case "express":
|
|
177
|
+
case "hono":
|
|
178
|
+
case "fastify": {
|
|
179
|
+
const authFile = join(d.root, `auth.${ext}`);
|
|
180
|
+
const cjs = !d.typescript && !d.esm;
|
|
181
|
+
files.push({ path: authFile, content: cjs ? PLAIN_AUTH_CJS : PLAIN_AUTH });
|
|
182
|
+
steps.push(`Add to your server, where the app is made:
|
|
183
|
+
|
|
184
|
+
${serverLines(d.framework, cjs)}`, `Start the application: ${dev}`, `Open ${hosted}`);
|
|
185
|
+
return { files, steps, envFile: ".env", open: hosted };
|
|
186
|
+
}
|
|
187
|
+
default:
|
|
188
|
+
steps.push('Serve auth.handler(request) under /api/auth/* (see the Auth SDK guide, "Mount the route").');
|
|
189
|
+
return { files, steps, envFile: ".env", open: hosted };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function serverLines(f, cjs) {
|
|
193
|
+
const from = "./auth";
|
|
194
|
+
if (f === "express") {
|
|
195
|
+
return cjs ? ` const { cauthyExpress } = require('cauthy-auth/express')
|
|
196
|
+
const { auth } = require('${from}')
|
|
197
|
+
app.use(cauthyExpress(auth))` : ` import { cauthyExpress } from 'cauthy-auth/express'
|
|
198
|
+
import { auth } from '${from}'
|
|
199
|
+
app.use(cauthyExpress(auth))`;
|
|
200
|
+
}
|
|
201
|
+
if (f === "hono") {
|
|
202
|
+
return cjs ? ` const { cauthyHono } = require('cauthy-auth/hono')
|
|
203
|
+
const { auth } = require('${from}')
|
|
204
|
+
app.use(cauthyHono(auth))` : ` import { cauthyHono } from 'cauthy-auth/hono'
|
|
205
|
+
import { auth } from '${from}'
|
|
206
|
+
app.use(cauthyHono(auth))`;
|
|
207
|
+
}
|
|
208
|
+
return cjs ? ` const { cauthyFastify } = require('cauthy-auth/fastify')
|
|
209
|
+
const { auth } = require('${from}')
|
|
210
|
+
await app.register(cauthyFastify, { auth })` : ` import { cauthyFastify } from 'cauthy-auth/fastify'
|
|
211
|
+
import { auth } from '${from}'
|
|
212
|
+
await app.register(cauthyFastify, { auth })`;
|
|
213
|
+
}
|
|
214
|
+
var NEXT_AUTH = `// createCauthy() reads CAUTHY_ISSUER, CAUTHY_CLIENT_ID, CAUTHY_CLIENT_SECRET,
|
|
215
|
+
// CAUTHY_SECRET and CAUTHY_TENANT from the environment.
|
|
216
|
+
import { createCauthy } from 'cauthy-auth'
|
|
217
|
+
import { cauthyNext } from 'cauthy-auth/next'
|
|
218
|
+
|
|
219
|
+
export const auth = createCauthy()
|
|
220
|
+
export const cauthy = cauthyNext(auth)
|
|
221
|
+
`;
|
|
222
|
+
var PLAIN_AUTH = `// createCauthy() reads CAUTHY_ISSUER, CAUTHY_CLIENT_ID, CAUTHY_CLIENT_SECRET,
|
|
223
|
+
// CAUTHY_SECRET and CAUTHY_TENANT from the environment.
|
|
224
|
+
import { createCauthy } from 'cauthy-auth'
|
|
225
|
+
|
|
226
|
+
export const auth = createCauthy()
|
|
227
|
+
`;
|
|
228
|
+
var PLAIN_AUTH_CJS = `// createCauthy() reads CAUTHY_ISSUER, CAUTHY_CLIENT_ID, CAUTHY_CLIENT_SECRET,
|
|
229
|
+
// CAUTHY_SECRET and CAUTHY_TENANT from the environment.
|
|
230
|
+
const { createCauthy } = require('cauthy-auth')
|
|
231
|
+
|
|
232
|
+
exports.auth = createCauthy()
|
|
233
|
+
`;
|
|
234
|
+
function reactSignInPage(head, declaration, tail = "") {
|
|
235
|
+
return `${head}// The application's own sign-in page. <SignIn /> is the ready-made form: the
|
|
236
|
+
// workspace's providers, passkeys, the second step and a sign-in link, drawn
|
|
237
|
+
// with the look set in Cauthy's dashboard. useAuth() has the same functions
|
|
238
|
+
// for a form of your own. Move <CauthyProvider> to the application's root
|
|
239
|
+
// to read the session on every page.
|
|
240
|
+
import { CauthyProvider, SignIn } from 'cauthy-auth/react'
|
|
241
|
+
import 'cauthy-auth/styles.css'
|
|
242
|
+
${tail}
|
|
243
|
+
${declaration}() {
|
|
244
|
+
return (
|
|
245
|
+
<main style={{ maxWidth: 380, margin: '64px auto', padding: '0 16px' }}>
|
|
246
|
+
<CauthyProvider>
|
|
247
|
+
<SignIn returnTo="/" />
|
|
248
|
+
</CauthyProvider>
|
|
249
|
+
</main>
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
`;
|
|
253
|
+
}
|
|
254
|
+
|
|
54
255
|
// src/permissions.ts
|
|
55
256
|
var IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
56
257
|
function policySource(system, roles) {
|
|
@@ -707,14 +908,19 @@ function createSystem(given = {}) {
|
|
|
707
908
|
}
|
|
708
909
|
|
|
709
910
|
// src/cli.ts
|
|
710
|
-
var INIT_HELP = `cauthy-auth init
|
|
911
|
+
var INIT_HELP = `cauthy-auth init [options]
|
|
711
912
|
|
|
712
|
-
Connects an application to Cauthy
|
|
713
|
-
|
|
913
|
+
Connects an application to Cauthy and leaves signing in working. Run it in
|
|
914
|
+
the application's folder: it reads which framework the application is made
|
|
915
|
+
with (Next.js, TanStack Start, SvelteKit, Express, Hono, Fastify), makes its
|
|
916
|
+
client in Cauthy, writes the variables to its env file, and lays down the
|
|
917
|
+
route and a sign-in page. A file that is already there is never written over.
|
|
714
918
|
|
|
715
919
|
--system <name> The system's name: lowercase letters, digits, - or _.
|
|
920
|
+
Default: the package's name.
|
|
716
921
|
--app-url <address> The application's public address: https://app.example.com,
|
|
717
|
-
or http://localhost:3000 while developing.
|
|
922
|
+
or http://localhost:3000 while developing. Default: the
|
|
923
|
+
framework's address while developing.
|
|
718
924
|
--name <name> Shown to people on Cauthy's pages. Default: the system's name.
|
|
719
925
|
--callback-path <path> Where the application serves the return from a sign-in.
|
|
720
926
|
Default: /api/auth/callback.
|
|
@@ -722,35 +928,78 @@ come back to and where it hears events, and prints the variables to set.
|
|
|
722
928
|
Cauthy's events (verify them with auth.webhook).
|
|
723
929
|
--jwks-uri <address> Where the application publishes its keys, to prove
|
|
724
930
|
itself with a signature instead of its secret.
|
|
725
|
-
--write <file>
|
|
726
|
-
|
|
931
|
+
--write <file> The env file to add the variables to. Default: .env.local
|
|
932
|
+
for Next.js, .env otherwise.
|
|
933
|
+
--print Print the variables instead of writing them.
|
|
934
|
+
--no-files Connect only: write no route and no page.
|
|
935
|
+
--reconnect Connect again although the env file has Cauthy's
|
|
936
|
+
variables: a new client, with a new secret.
|
|
727
937
|
|
|
728
938
|
Needs CAUTHY_ISSUER, CAUTHY_TENANT (the workspace's slug) and CAUTHY_TOKEN (a
|
|
729
939
|
personal access token with systems:connect).
|
|
730
940
|
`;
|
|
941
|
+
function readJson(file) {
|
|
942
|
+
try {
|
|
943
|
+
return JSON.parse((0, import_node_fs.readFileSync)(file, "utf8"));
|
|
944
|
+
} catch {
|
|
945
|
+
return null;
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
function ignored(file) {
|
|
949
|
+
let lines;
|
|
950
|
+
try {
|
|
951
|
+
lines = (0, import_node_fs.readFileSync)(".gitignore", "utf8").split(/\r?\n/).map((l) => l.trim().replace(/^\//, ""));
|
|
952
|
+
} catch {
|
|
953
|
+
return false;
|
|
954
|
+
}
|
|
955
|
+
return lines.some((l) => l === file || l === ".env*" || l === "*.env" || file.endsWith(".local") && (l === "*.local" || l === ".env*.local"));
|
|
956
|
+
}
|
|
731
957
|
async function init(argv) {
|
|
732
958
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
733
959
|
process.stdout.write(INIT_HELP);
|
|
734
960
|
return;
|
|
735
961
|
}
|
|
736
962
|
const given = {};
|
|
737
|
-
const
|
|
963
|
+
const valued = ["--system", "--app-url", "--name", "--callback-path", "--events-url", "--jwks-uri", "--write"];
|
|
964
|
+
const switches = ["--print", "--no-files", "--reconnect"];
|
|
738
965
|
for (let i = 0; i < argv.length; i++) {
|
|
739
966
|
const arg = argv[i];
|
|
740
|
-
if (
|
|
967
|
+
if (switches.includes(arg)) given[arg] = "yes";
|
|
968
|
+
else if (valued.includes(arg)) given[arg] = argv[++i] ?? fail(`${arg} needs a value`);
|
|
969
|
+
else fail(`unknown option ${arg}
|
|
741
970
|
|
|
742
971
|
${INIT_HELP}`);
|
|
743
|
-
given[arg] = argv[++i] ?? fail(`${arg} needs a value`);
|
|
744
972
|
}
|
|
745
|
-
|
|
973
|
+
const pkg = readJson("package.json");
|
|
974
|
+
const found = pkg ? detect(pkg, import_node_fs.existsSync) : null;
|
|
975
|
+
const plan = found ? scaffold(found) : null;
|
|
976
|
+
const system = given["--system"] ?? found?.system;
|
|
977
|
+
const appUrl = given["--app-url"] ?? found?.devUrl;
|
|
978
|
+
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]}.
|
|
980
|
+
`);
|
|
981
|
+
const target = given["--write"] ?? plan?.envFile;
|
|
982
|
+
let had = "";
|
|
983
|
+
try {
|
|
984
|
+
had = target ? (0, import_node_fs.readFileSync)(target, "utf8") : "";
|
|
985
|
+
} catch {
|
|
986
|
+
}
|
|
987
|
+
const already = /^CAUTHY_CLIENT_ID=/m.test(had);
|
|
988
|
+
if (already && !given["--reconnect"]) {
|
|
989
|
+
process.stdout.write(`Already connected: ${target} has Cauthy's variables. (--reconnect makes a new client.)
|
|
990
|
+
`);
|
|
991
|
+
return writeFiles(plan, given["--no-files"] !== void 0);
|
|
992
|
+
}
|
|
746
993
|
const done = await connectSystem({
|
|
747
|
-
system
|
|
748
|
-
appUrl
|
|
994
|
+
system,
|
|
995
|
+
appUrl,
|
|
749
996
|
name: given["--name"],
|
|
750
997
|
callbackPath: given["--callback-path"],
|
|
751
998
|
eventsUrl: given["--events-url"],
|
|
752
999
|
jwksUri: given["--jwks-uri"]
|
|
753
1000
|
});
|
|
1001
|
+
const tenant = process.env.CAUTHY_TENANT;
|
|
1002
|
+
if (tenant && !done.env.CAUTHY_TENANT) done.env.CAUTHY_TENANT = tenant;
|
|
754
1003
|
const vars = envFile(done.env);
|
|
755
1004
|
process.stdout.write(`Connected: the system ${done.system} is the client ${done.clientId}.
|
|
756
1005
|
`);
|
|
@@ -758,25 +1007,55 @@ ${INIT_HELP}`);
|
|
|
758
1007
|
`);
|
|
759
1008
|
if (done.webhook) process.stdout.write(` events are sent to ${done.webhook.url}
|
|
760
1009
|
`);
|
|
761
|
-
if (given["--
|
|
762
|
-
(
|
|
763
|
-
|
|
764
|
-
The variables were added to ${given["--write"]}. Keep that file out of the repository.
|
|
1010
|
+
if (given["--print"] || !target || already) {
|
|
1011
|
+
if (already) process.stdout.write(`
|
|
1012
|
+
${target} already has Cauthy's variables: replace them with these by hand.
|
|
765
1013
|
`);
|
|
766
|
-
} else {
|
|
767
1014
|
process.stdout.write(`
|
|
768
1015
|
Set these where the application runs. The secret is shown this once:
|
|
769
1016
|
|
|
770
1017
|
${vars}`);
|
|
1018
|
+
} else {
|
|
1019
|
+
(0, import_node_fs.appendFileSync)(target, (had && !had.endsWith("\n") ? "\n" : "") + vars, { mode: 384 });
|
|
1020
|
+
process.stdout.write(`
|
|
1021
|
+
The variables were added to ${target}.
|
|
1022
|
+
`);
|
|
1023
|
+
if (!ignored(target)) {
|
|
1024
|
+
(0, import_node_fs.appendFileSync)(".gitignore", `${(0, import_node_fs.existsSync)(".gitignore") ? "\n" : ""}${target}
|
|
1025
|
+
`);
|
|
1026
|
+
process.stdout.write(`${target} was added to .gitignore: it holds the client's secret.
|
|
1027
|
+
`);
|
|
1028
|
+
}
|
|
771
1029
|
}
|
|
772
|
-
|
|
1030
|
+
writeFiles(plan, given["--no-files"] !== void 0);
|
|
1031
|
+
}
|
|
1032
|
+
function writeFiles(plan, none) {
|
|
1033
|
+
if (!plan || none) {
|
|
1034
|
+
process.stdout.write(`
|
|
773
1035
|
Then, in the application:
|
|
774
1036
|
|
|
775
1037
|
import { createCauthy } from 'cauthy-auth'
|
|
776
1038
|
export const auth = createCauthy()
|
|
777
1039
|
|
|
778
|
-
and serve auth.handler(request) under
|
|
1040
|
+
and serve auth.handler(request) under /api/auth/*.
|
|
1041
|
+
`);
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
process.stdout.write("\n");
|
|
1045
|
+
for (const f of plan.files) {
|
|
1046
|
+
if ((0, import_node_fs.existsSync)(f.path)) {
|
|
1047
|
+
process.stdout.write(` left as it was ${f.path} (it is already there)
|
|
779
1048
|
`);
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path2.dirname)(f.path) || ".", { recursive: true });
|
|
1052
|
+
(0, import_node_fs.writeFileSync)(f.path, f.content);
|
|
1053
|
+
process.stdout.write(` wrote ${f.path}
|
|
1054
|
+
`);
|
|
1055
|
+
}
|
|
1056
|
+
process.stdout.write("\nWhat is left:\n\n");
|
|
1057
|
+
plan.steps.forEach((step, i) => process.stdout.write(`${i + 1}. ${step}
|
|
1058
|
+
`));
|
|
780
1059
|
}
|
|
781
1060
|
var POLICY_HELP = `cauthy-auth policy --pull [--out <file.ts>] [--system <name>]
|
|
782
1061
|
cauthy-auth policy --file <roles.json> [--system <name>] [--push]
|
|
@@ -869,7 +1148,7 @@ ${POLICY_HELP}`);
|
|
|
869
1148
|
process.stdout.write("Not sent. Add --push to give it to Cauthy.\n");
|
|
870
1149
|
}
|
|
871
1150
|
}
|
|
872
|
-
var HELP = `cauthy-auth init \u2026 connect an application
|
|
1151
|
+
var HELP = `cauthy-auth init \u2026 connect an application and leave signing in working (cauthy-auth init --help)
|
|
873
1152
|
cauthy-auth policy \u2026 load a system's catalog of roles (cauthy-auth policy --help)
|
|
874
1153
|
cauthy-auth theme [options]
|
|
875
1154
|
|
package/dist/client.d.mts
CHANGED
|
@@ -4,6 +4,15 @@ import { A as AuthField } from './validate-B_iC-4y8.mjs';
|
|
|
4
4
|
export { F as FieldProblem, M as MIN_PASSWORD, c as checkCode, a as checkEmail, b as checkName, d as checkNewPassword, e as checkPassword } from './validate-B_iC-4y8.mjs';
|
|
5
5
|
import 'jose';
|
|
6
6
|
|
|
7
|
+
/** Whether this browser can use a passkey at all. */
|
|
8
|
+
declare function canUsePasskeys(): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Whether this browser offers passkeys in the autofill of a field marked
|
|
11
|
+
* `autocomplete="username webauthn"`, so the person picks one without
|
|
12
|
+
* looking for a button.
|
|
13
|
+
*/
|
|
14
|
+
declare function canAutofillPasskeys(): Promise<boolean>;
|
|
15
|
+
|
|
7
16
|
interface SessionState {
|
|
8
17
|
/** The signed-in person and their organizations, or null. */
|
|
9
18
|
session: PublicSession | null;
|
|
@@ -70,8 +79,29 @@ interface AuthMethods {
|
|
|
70
79
|
/** Whether people may create an account themselves. */
|
|
71
80
|
signUp: boolean;
|
|
72
81
|
magicLink: boolean;
|
|
73
|
-
/** Whether to offer a passkey.
|
|
82
|
+
/** Whether to offer a passkey. */
|
|
74
83
|
passkey: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Whether the passkey is used on this page itself. False: `signIn.passkey`
|
|
86
|
+
* sends the person to Cauthy's page for that step alone, and they come back
|
|
87
|
+
* signed in. That happens only when this page is outside the domain the
|
|
88
|
+
* workspace's passkeys belong to.
|
|
89
|
+
*/
|
|
90
|
+
passkeyHere: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Whether Cauthy's page can use the passkey, for that step alone: where a
|
|
93
|
+
* page that cannot, or a browser that refuses to here, sends the person.
|
|
94
|
+
*/
|
|
95
|
+
passkeyOnCauthy: boolean;
|
|
96
|
+
}
|
|
97
|
+
/** A passkey of the signed-in account. Moments are UTC. */
|
|
98
|
+
interface Passkey {
|
|
99
|
+
id: string;
|
|
100
|
+
/** What the person called it ("My laptop"), or "". */
|
|
101
|
+
name: string;
|
|
102
|
+
createdAt: string;
|
|
103
|
+
/** When it last signed the person in, or null. */
|
|
104
|
+
lastUsedAt: string | null;
|
|
75
105
|
}
|
|
76
106
|
/** A person's own account, as their account screen shows it. */
|
|
77
107
|
interface Account {
|
|
@@ -175,6 +205,22 @@ interface AccountClient {
|
|
|
175
205
|
sessions(): Promise<AccountSession[]>;
|
|
176
206
|
/** Signs the account out everywhere, here too. */
|
|
177
207
|
signOutEverywhere(): Promise<AuthResult<'signed_out'>>;
|
|
208
|
+
/** The account's passkeys. */
|
|
209
|
+
passkeys(): Promise<Passkey[]>;
|
|
210
|
+
/**
|
|
211
|
+
* Makes a passkey on this device, on this page: the browser asks the person
|
|
212
|
+
* for their fingerprint, face or screen lock. An account with a password
|
|
213
|
+
* proves it again first. `cancelled`: the person closed the browser's prompt.
|
|
214
|
+
*/
|
|
215
|
+
addPasskey(form?: {
|
|
216
|
+
password?: string;
|
|
217
|
+
name?: string;
|
|
218
|
+
}): Promise<AuthResult<'added', {
|
|
219
|
+
passkey: Passkey;
|
|
220
|
+
}>>;
|
|
221
|
+
removePasskey(id: string): Promise<AuthResult<'removed', {
|
|
222
|
+
id: string;
|
|
223
|
+
}>>;
|
|
178
224
|
}
|
|
179
225
|
interface AuthClient {
|
|
180
226
|
signIn: {
|
|
@@ -207,12 +253,49 @@ interface AuthClient {
|
|
|
207
253
|
}): Promise<AuthResult<'signed_in', {
|
|
208
254
|
session: PublicSession;
|
|
209
255
|
}>>;
|
|
210
|
-
/**
|
|
256
|
+
/**
|
|
257
|
+
* Signs in with a passkey, on this page: the browser asks the person for
|
|
258
|
+
* their fingerprint, face or screen lock. When this page cannot use the
|
|
259
|
+
* workspace's passkeys (`methods().passkeyHere` is false), the browser goes
|
|
260
|
+
* to Cauthy's page for that step alone and comes back signed in, at
|
|
261
|
+
* `returnTo`: then the answer is `redirected`. `cancelled`: the person
|
|
262
|
+
* closed the browser's prompt.
|
|
263
|
+
*/
|
|
211
264
|
passkey(options?: {
|
|
212
265
|
returnTo?: string;
|
|
213
266
|
locale?: string;
|
|
214
|
-
}):
|
|
215
|
-
|
|
267
|
+
}): Promise<AuthResult<'signed_in', {
|
|
268
|
+
session: PublicSession;
|
|
269
|
+
}> | AuthResult<'redirected'>>;
|
|
270
|
+
/**
|
|
271
|
+
* Offers the person's passkeys in the browser's autofill of the email
|
|
272
|
+
* field, which must carry `autocomplete="username webauthn"`, and signs in
|
|
273
|
+
* with the one they pick. It waits until they pick one or `signal`
|
|
274
|
+
* aborts: abort it before calling `passkey` or sending the form.
|
|
275
|
+
* `cancelled`: aborted, or this browser or page cannot (then there is
|
|
276
|
+
* nothing to offer, and the passkey button still works).
|
|
277
|
+
*/
|
|
278
|
+
passkeyAutofill(options?: {
|
|
279
|
+
signal?: AbortSignal;
|
|
280
|
+
}): Promise<AuthResult<'signed_in', {
|
|
281
|
+
session: PublicSession;
|
|
282
|
+
}>>;
|
|
283
|
+
/**
|
|
284
|
+
* Google One Tap: Google's prompt over the page offers the Google account
|
|
285
|
+
* the browser already has, and signs in with it in one tap, as the Google
|
|
286
|
+
* sign-in does (the same account). Needs the workspace's Google sign-in,
|
|
287
|
+
* and the page's address among the Google client's authorized JavaScript
|
|
288
|
+
* origins. Waits until the person picks an account or closes the prompt
|
|
289
|
+
* (`cancelled`), or `signal` aborts. `one_tap_unavailable`: the workspace
|
|
290
|
+
* has no Google sign-in.
|
|
291
|
+
*/
|
|
292
|
+
googleOneTap(options?: {
|
|
293
|
+
signal?: AbortSignal;
|
|
294
|
+
context?: 'signin' | 'signup' | 'use';
|
|
295
|
+
}): Promise<AuthResult<'signed_in', {
|
|
296
|
+
session: PublicSession;
|
|
297
|
+
}>>;
|
|
298
|
+
/** Where `passkey` sends the browser when this page cannot use the passkey itself. */
|
|
216
299
|
passkeyUrl(options?: {
|
|
217
300
|
returnTo?: string;
|
|
218
301
|
locale?: string;
|
|
@@ -277,4 +360,4 @@ interface AuthClientOptions {
|
|
|
277
360
|
}
|
|
278
361
|
declare function createAuthClient(options?: AuthClientOptions): AuthClient;
|
|
279
362
|
|
|
280
|
-
export { type Account, type AccountClient, type AccountSession, type AuthClient, type AuthClientOptions, type AuthError, AuthField, type AuthMethods, type AuthResult, type LinkedIdentity, type SessionState, type SessionStore, type SessionStoreOptions, createAuthClient, createSessionStore };
|
|
363
|
+
export { type Account, type AccountClient, type AccountSession, type AuthClient, type AuthClientOptions, type AuthError, AuthField, type AuthMethods, type AuthResult, type LinkedIdentity, type Passkey, type SessionState, type SessionStore, type SessionStoreOptions, canAutofillPasskeys, canUsePasskeys, createAuthClient, createSessionStore };
|
package/dist/client.d.ts
CHANGED
|
@@ -4,6 +4,15 @@ import { A as AuthField } from './validate-B_iC-4y8.js';
|
|
|
4
4
|
export { F as FieldProblem, M as MIN_PASSWORD, c as checkCode, a as checkEmail, b as checkName, d as checkNewPassword, e as checkPassword } from './validate-B_iC-4y8.js';
|
|
5
5
|
import 'jose';
|
|
6
6
|
|
|
7
|
+
/** Whether this browser can use a passkey at all. */
|
|
8
|
+
declare function canUsePasskeys(): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Whether this browser offers passkeys in the autofill of a field marked
|
|
11
|
+
* `autocomplete="username webauthn"`, so the person picks one without
|
|
12
|
+
* looking for a button.
|
|
13
|
+
*/
|
|
14
|
+
declare function canAutofillPasskeys(): Promise<boolean>;
|
|
15
|
+
|
|
7
16
|
interface SessionState {
|
|
8
17
|
/** The signed-in person and their organizations, or null. */
|
|
9
18
|
session: PublicSession | null;
|
|
@@ -70,8 +79,29 @@ interface AuthMethods {
|
|
|
70
79
|
/** Whether people may create an account themselves. */
|
|
71
80
|
signUp: boolean;
|
|
72
81
|
magicLink: boolean;
|
|
73
|
-
/** Whether to offer a passkey.
|
|
82
|
+
/** Whether to offer a passkey. */
|
|
74
83
|
passkey: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Whether the passkey is used on this page itself. False: `signIn.passkey`
|
|
86
|
+
* sends the person to Cauthy's page for that step alone, and they come back
|
|
87
|
+
* signed in. That happens only when this page is outside the domain the
|
|
88
|
+
* workspace's passkeys belong to.
|
|
89
|
+
*/
|
|
90
|
+
passkeyHere: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Whether Cauthy's page can use the passkey, for that step alone: where a
|
|
93
|
+
* page that cannot, or a browser that refuses to here, sends the person.
|
|
94
|
+
*/
|
|
95
|
+
passkeyOnCauthy: boolean;
|
|
96
|
+
}
|
|
97
|
+
/** A passkey of the signed-in account. Moments are UTC. */
|
|
98
|
+
interface Passkey {
|
|
99
|
+
id: string;
|
|
100
|
+
/** What the person called it ("My laptop"), or "". */
|
|
101
|
+
name: string;
|
|
102
|
+
createdAt: string;
|
|
103
|
+
/** When it last signed the person in, or null. */
|
|
104
|
+
lastUsedAt: string | null;
|
|
75
105
|
}
|
|
76
106
|
/** A person's own account, as their account screen shows it. */
|
|
77
107
|
interface Account {
|
|
@@ -175,6 +205,22 @@ interface AccountClient {
|
|
|
175
205
|
sessions(): Promise<AccountSession[]>;
|
|
176
206
|
/** Signs the account out everywhere, here too. */
|
|
177
207
|
signOutEverywhere(): Promise<AuthResult<'signed_out'>>;
|
|
208
|
+
/** The account's passkeys. */
|
|
209
|
+
passkeys(): Promise<Passkey[]>;
|
|
210
|
+
/**
|
|
211
|
+
* Makes a passkey on this device, on this page: the browser asks the person
|
|
212
|
+
* for their fingerprint, face or screen lock. An account with a password
|
|
213
|
+
* proves it again first. `cancelled`: the person closed the browser's prompt.
|
|
214
|
+
*/
|
|
215
|
+
addPasskey(form?: {
|
|
216
|
+
password?: string;
|
|
217
|
+
name?: string;
|
|
218
|
+
}): Promise<AuthResult<'added', {
|
|
219
|
+
passkey: Passkey;
|
|
220
|
+
}>>;
|
|
221
|
+
removePasskey(id: string): Promise<AuthResult<'removed', {
|
|
222
|
+
id: string;
|
|
223
|
+
}>>;
|
|
178
224
|
}
|
|
179
225
|
interface AuthClient {
|
|
180
226
|
signIn: {
|
|
@@ -207,12 +253,49 @@ interface AuthClient {
|
|
|
207
253
|
}): Promise<AuthResult<'signed_in', {
|
|
208
254
|
session: PublicSession;
|
|
209
255
|
}>>;
|
|
210
|
-
/**
|
|
256
|
+
/**
|
|
257
|
+
* Signs in with a passkey, on this page: the browser asks the person for
|
|
258
|
+
* their fingerprint, face or screen lock. When this page cannot use the
|
|
259
|
+
* workspace's passkeys (`methods().passkeyHere` is false), the browser goes
|
|
260
|
+
* to Cauthy's page for that step alone and comes back signed in, at
|
|
261
|
+
* `returnTo`: then the answer is `redirected`. `cancelled`: the person
|
|
262
|
+
* closed the browser's prompt.
|
|
263
|
+
*/
|
|
211
264
|
passkey(options?: {
|
|
212
265
|
returnTo?: string;
|
|
213
266
|
locale?: string;
|
|
214
|
-
}):
|
|
215
|
-
|
|
267
|
+
}): Promise<AuthResult<'signed_in', {
|
|
268
|
+
session: PublicSession;
|
|
269
|
+
}> | AuthResult<'redirected'>>;
|
|
270
|
+
/**
|
|
271
|
+
* Offers the person's passkeys in the browser's autofill of the email
|
|
272
|
+
* field, which must carry `autocomplete="username webauthn"`, and signs in
|
|
273
|
+
* with the one they pick. It waits until they pick one or `signal`
|
|
274
|
+
* aborts: abort it before calling `passkey` or sending the form.
|
|
275
|
+
* `cancelled`: aborted, or this browser or page cannot (then there is
|
|
276
|
+
* nothing to offer, and the passkey button still works).
|
|
277
|
+
*/
|
|
278
|
+
passkeyAutofill(options?: {
|
|
279
|
+
signal?: AbortSignal;
|
|
280
|
+
}): Promise<AuthResult<'signed_in', {
|
|
281
|
+
session: PublicSession;
|
|
282
|
+
}>>;
|
|
283
|
+
/**
|
|
284
|
+
* Google One Tap: Google's prompt over the page offers the Google account
|
|
285
|
+
* the browser already has, and signs in with it in one tap, as the Google
|
|
286
|
+
* sign-in does (the same account). Needs the workspace's Google sign-in,
|
|
287
|
+
* and the page's address among the Google client's authorized JavaScript
|
|
288
|
+
* origins. Waits until the person picks an account or closes the prompt
|
|
289
|
+
* (`cancelled`), or `signal` aborts. `one_tap_unavailable`: the workspace
|
|
290
|
+
* has no Google sign-in.
|
|
291
|
+
*/
|
|
292
|
+
googleOneTap(options?: {
|
|
293
|
+
signal?: AbortSignal;
|
|
294
|
+
context?: 'signin' | 'signup' | 'use';
|
|
295
|
+
}): Promise<AuthResult<'signed_in', {
|
|
296
|
+
session: PublicSession;
|
|
297
|
+
}>>;
|
|
298
|
+
/** Where `passkey` sends the browser when this page cannot use the passkey itself. */
|
|
216
299
|
passkeyUrl(options?: {
|
|
217
300
|
returnTo?: string;
|
|
218
301
|
locale?: string;
|
|
@@ -277,4 +360,4 @@ interface AuthClientOptions {
|
|
|
277
360
|
}
|
|
278
361
|
declare function createAuthClient(options?: AuthClientOptions): AuthClient;
|
|
279
362
|
|
|
280
|
-
export { type Account, type AccountClient, type AccountSession, type AuthClient, type AuthClientOptions, type AuthError, AuthField, type AuthMethods, type AuthResult, type LinkedIdentity, type SessionState, type SessionStore, type SessionStoreOptions, createAuthClient, createSessionStore };
|
|
363
|
+
export { type Account, type AccountClient, type AccountSession, type AuthClient, type AuthClientOptions, type AuthError, AuthField, type AuthMethods, type AuthResult, type LinkedIdentity, type Passkey, type SessionState, type SessionStore, type SessionStoreOptions, canAutofillPasskeys, canUsePasskeys, createAuthClient, createSessionStore };
|