cauthy-auth 0.11.0 → 0.13.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 +94 -1
- package/dist/client.d.ts +94 -1
- package/dist/client.js +107 -9
- package/dist/client.js.map +1 -1
- package/dist/client.mjs +106 -9
- package/dist/client.mjs.map +1 -1
- package/dist/index.js +67 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +67 -1
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +123 -1
- package/dist/react.d.ts +123 -1
- package/dist/react.js +358 -17
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +356 -17
- package/dist/react.mjs.map +1 -1
- package/dist/styles.css +5 -0
- package/dist/svelte.js +105 -9
- package/dist/svelte.js.map +1 -1
- package/dist/svelte.mjs +105 -9
- package/dist/svelte.mjs.map +1 -1
- package/dist/vue.js +105 -9
- package/dist/vue.js.map +1 -1
- package/dist/vue.mjs +105 -9
- 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
|
@@ -6,6 +6,12 @@ import 'jose';
|
|
|
6
6
|
|
|
7
7
|
/** Whether this browser can use a passkey at all. */
|
|
8
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>;
|
|
9
15
|
|
|
10
16
|
interface SessionState {
|
|
11
17
|
/** The signed-in person and their organizations, or null. */
|
|
@@ -87,6 +93,13 @@ interface AuthMethods {
|
|
|
87
93
|
* page that cannot, or a browser that refuses to here, sends the person.
|
|
88
94
|
*/
|
|
89
95
|
passkeyOnCauthy: boolean;
|
|
96
|
+
/**
|
|
97
|
+
* The workspace's Cloudflare Turnstile site key, when it asks for the
|
|
98
|
+
* challenge: signing in, creating an account, and asking for a sign-in link
|
|
99
|
+
* or a new password then take its answer (`captchaToken`). The ready-made
|
|
100
|
+
* forms show it themselves.
|
|
101
|
+
*/
|
|
102
|
+
captchaSiteKey?: string;
|
|
90
103
|
}
|
|
91
104
|
/** A passkey of the signed-in account. Moments are UTC. */
|
|
92
105
|
interface Passkey {
|
|
@@ -128,6 +141,17 @@ interface LinkedIdentity {
|
|
|
128
141
|
/** When it was linked, in UTC (ISO 8601). */
|
|
129
142
|
linkedAt: string;
|
|
130
143
|
}
|
|
144
|
+
/** How an account takes its second step. */
|
|
145
|
+
interface SecondStep {
|
|
146
|
+
totp: boolean;
|
|
147
|
+
/** The phone codes are sent to, masked (`+57 ••• ••• 4567`). */
|
|
148
|
+
phone: {
|
|
149
|
+
number: string;
|
|
150
|
+
channel: string;
|
|
151
|
+
} | null;
|
|
152
|
+
/** The ways the workspace sends codes to phones. */
|
|
153
|
+
phoneChannels: string[];
|
|
154
|
+
}
|
|
131
155
|
interface AccountClient {
|
|
132
156
|
/** The account, or null when nobody is signed in. */
|
|
133
157
|
get(): Promise<Account | null>;
|
|
@@ -166,6 +190,30 @@ interface AccountClient {
|
|
|
166
190
|
}): Promise<AuthResult<'renewed', {
|
|
167
191
|
recoveryCodes: string[];
|
|
168
192
|
}>>;
|
|
193
|
+
/**
|
|
194
|
+
* How the account takes its second step: an authenticator app, a phone
|
|
195
|
+
* (masked), and the ways the workspace sends codes to phones (`sms`,
|
|
196
|
+
* `whatsapp`; none when it has no Twilio account for it).
|
|
197
|
+
*/
|
|
198
|
+
factors(): Promise<SecondStep | null>;
|
|
199
|
+
/** Adds a phone as the second step: a code is sent to it, for `verifyPhone`. */
|
|
200
|
+
addPhone(form: {
|
|
201
|
+
phone: string;
|
|
202
|
+
channel: 'sms' | 'whatsapp';
|
|
203
|
+
password: string;
|
|
204
|
+
}): Promise<AuthResult<'sent', {
|
|
205
|
+
sentTo: string;
|
|
206
|
+
channel: string;
|
|
207
|
+
}>>;
|
|
208
|
+
/** Proves the phone with the code it received. `recoveryCodes` come with a first second step, shown this once. */
|
|
209
|
+
verifyPhone(form: {
|
|
210
|
+
code: string;
|
|
211
|
+
}): Promise<AuthResult<'enabled', {
|
|
212
|
+
recoveryCodes: string[];
|
|
213
|
+
}>>;
|
|
214
|
+
removePhone(form: {
|
|
215
|
+
password: string;
|
|
216
|
+
}): Promise<AuthResult<'removed'>>;
|
|
169
217
|
};
|
|
170
218
|
/**
|
|
171
219
|
* Asks to sign in with another address. Nothing changes yet: the new address
|
|
@@ -222,6 +270,7 @@ interface AuthClient {
|
|
|
222
270
|
email(form: {
|
|
223
271
|
email: string;
|
|
224
272
|
password: string;
|
|
273
|
+
captchaToken?: string;
|
|
225
274
|
}): Promise<AuthResult<'signed_in', {
|
|
226
275
|
session: PublicSession;
|
|
227
276
|
}> | AuthResult<'mfa_required'>>;
|
|
@@ -231,9 +280,23 @@ interface AuthClient {
|
|
|
231
280
|
}): Promise<AuthResult<'signed_in', {
|
|
232
281
|
session: PublicSession;
|
|
233
282
|
}>>;
|
|
283
|
+
/** While the second step is asked for: whether the account has an authenticator app, and the phone (masked) a code can be sent to. */
|
|
284
|
+
mfaMethods(): Promise<AuthResult<'ok', {
|
|
285
|
+
totp: boolean;
|
|
286
|
+
phone: {
|
|
287
|
+
number: string;
|
|
288
|
+
channel: string;
|
|
289
|
+
} | null;
|
|
290
|
+
}>>;
|
|
291
|
+
/** Sends a code to the account's phone, by SMS or WhatsApp; `mfa` takes it. A few per sign-in. */
|
|
292
|
+
mfaSendCode(): Promise<AuthResult<'sent', {
|
|
293
|
+
sentTo: string;
|
|
294
|
+
channel: string;
|
|
295
|
+
}>>;
|
|
234
296
|
/** Mails a link that signs the person in. The answer is the same whether or not the address has an account. */
|
|
235
297
|
magicLink(form: {
|
|
236
298
|
email: string;
|
|
299
|
+
captchaToken?: string;
|
|
237
300
|
}): Promise<AuthResult<'sent'>>;
|
|
238
301
|
/** Signs in with the token the mailed link opened the page with (`ml_token`). */
|
|
239
302
|
magicLinkVerify(form: {
|
|
@@ -261,6 +324,34 @@ interface AuthClient {
|
|
|
261
324
|
}): Promise<AuthResult<'signed_in', {
|
|
262
325
|
session: PublicSession;
|
|
263
326
|
}> | AuthResult<'redirected'>>;
|
|
327
|
+
/**
|
|
328
|
+
* Offers the person's passkeys in the browser's autofill of the email
|
|
329
|
+
* field, which must carry `autocomplete="username webauthn"`, and signs in
|
|
330
|
+
* with the one they pick. It waits until they pick one or `signal`
|
|
331
|
+
* aborts: abort it before calling `passkey` or sending the form.
|
|
332
|
+
* `cancelled`: aborted, or this browser or page cannot (then there is
|
|
333
|
+
* nothing to offer, and the passkey button still works).
|
|
334
|
+
*/
|
|
335
|
+
passkeyAutofill(options?: {
|
|
336
|
+
signal?: AbortSignal;
|
|
337
|
+
}): Promise<AuthResult<'signed_in', {
|
|
338
|
+
session: PublicSession;
|
|
339
|
+
}>>;
|
|
340
|
+
/**
|
|
341
|
+
* Google One Tap: Google's prompt over the page offers the Google account
|
|
342
|
+
* the browser already has, and signs in with it in one tap, as the Google
|
|
343
|
+
* sign-in does (the same account). Needs the workspace's Google sign-in,
|
|
344
|
+
* and the page's address among the Google client's authorized JavaScript
|
|
345
|
+
* origins. Waits until the person picks an account or closes the prompt
|
|
346
|
+
* (`cancelled`), or `signal` aborts. `one_tap_unavailable`: the workspace
|
|
347
|
+
* has no Google sign-in.
|
|
348
|
+
*/
|
|
349
|
+
googleOneTap(options?: {
|
|
350
|
+
signal?: AbortSignal;
|
|
351
|
+
context?: 'signin' | 'signup' | 'use';
|
|
352
|
+
}): Promise<AuthResult<'signed_in', {
|
|
353
|
+
session: PublicSession;
|
|
354
|
+
}>>;
|
|
264
355
|
/** Where `passkey` sends the browser when this page cannot use the passkey itself. */
|
|
265
356
|
passkeyUrl(options?: {
|
|
266
357
|
returnTo?: string;
|
|
@@ -281,6 +372,7 @@ interface AuthClient {
|
|
|
281
372
|
email: string;
|
|
282
373
|
password: string;
|
|
283
374
|
name?: string;
|
|
375
|
+
captchaToken?: string;
|
|
284
376
|
}): Promise<AuthResult<'signed_in', {
|
|
285
377
|
session: PublicSession;
|
|
286
378
|
}> | AuthResult<'verification_required', {
|
|
@@ -290,6 +382,7 @@ interface AuthClient {
|
|
|
290
382
|
/** Mails a link to choose a new password. The answer is the same whether or not the address has an account. */
|
|
291
383
|
forgotPassword(form: {
|
|
292
384
|
email: string;
|
|
385
|
+
captchaToken?: string;
|
|
293
386
|
}): Promise<AuthResult<'sent'>>;
|
|
294
387
|
/** Sets the new password, with the token the mailed link opened the page with (`token`). */
|
|
295
388
|
resetPassword(form: {
|
|
@@ -326,4 +419,4 @@ interface AuthClientOptions {
|
|
|
326
419
|
}
|
|
327
420
|
declare function createAuthClient(options?: AuthClientOptions): AuthClient;
|
|
328
421
|
|
|
329
|
-
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, canUsePasskeys, createAuthClient, createSessionStore };
|
|
422
|
+
export { type Account, type AccountClient, type AccountSession, type AuthClient, type AuthClientOptions, type AuthError, AuthField, type AuthMethods, type AuthResult, type LinkedIdentity, type Passkey, type SecondStep, type SessionState, type SessionStore, type SessionStoreOptions, canAutofillPasskeys, canUsePasskeys, createAuthClient, createSessionStore };
|