@madauth/server 0.1.0 → 0.2.0-beta.1
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 +17 -7
- package/dist/bin.js +284 -22
- package/dist/{chunk-V2XKGETK.js → chunk-5CKEMC6M.js} +163 -28
- package/dist/{chunk-AVZE6FFR.js → chunk-6EJDVZCD.js} +8 -3
- package/dist/{chunk-PRRKLEW7.js → chunk-7CTAB3QV.js} +21 -0
- package/dist/{chunk-3WIWWKRI.js → chunk-CNL562LZ.js} +17 -42
- package/dist/chunk-XNBWZZHT.js +60 -0
- package/dist/{chunk-TEMZRMFF.js → chunk-YALS7OMV.js} +162 -42
- package/dist/{config-KQvE2imU.d.ts → config-BkxADgtp.d.ts} +16 -4
- package/dist/dynamodb.js +244 -0
- package/dist/entry/azure.d.ts +5 -5
- package/dist/entry/azure.js +9 -7
- package/dist/entry/lambda.d.ts +5 -5
- package/dist/entry/lambda.js +9 -7
- package/dist/entry/node.d.ts +5 -8
- package/dist/entry/node.js +8 -6
- package/dist/index.d.ts +12 -7
- package/dist/index.js +12 -8
- package/dist/{schema-Dotc6LU7.d.ts → schema-BSgYVRpd.d.ts} +28 -1
- package/dist/sqlite.js +7 -4
- package/dist/standalone/azure.mjs +611 -78
- package/dist/standalone/lambda.mjs +611 -78
- package/dist/standalone/main.mjs +29508 -592
- package/dist/store/dynamodb.d.ts +19 -0
- package/dist/store/sqlite.d.ts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +65 -2
- package/dist/{user-3DDyggFV.d.ts → user-C1A3BfOo.d.ts} +2 -0
- package/dist/verify.d.ts +1 -1
- package/dist/verify.js +1 -1
- package/dist/{webhook-CT80emTQ.d.ts → webhook-ltwulgcB.d.ts} +3 -3
- package/dist/webhook.d.ts +1 -1
- package/dist/webhook.js +1 -1
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -1,27 +1,35 @@
|
|
|
1
1
|
# @madauth/server
|
|
2
2
|
|
|
3
|
-
The server of [madAuth](https://github.com/inouiw/madAuth), a self-hostable alternative to Cognito / Auth0: Google and e-mail & password sign-in with madAuth sessions. It runs in Docker, on AWS Lambda, on Azure Functions or inside your own Node server.
|
|
3
|
+
The server of [madAuth](https://github.com/inouiw/madAuth), a self-hostable alternative to Cognito / Auth0: Google and e-mail & password sign-in with madAuth sessions. It runs with Node, in Docker, on AWS Lambda, on Azure Functions or inside your own Node server.
|
|
4
4
|
|
|
5
5
|
The browser side is [`@madauth/web`](https://www.npmjs.com/package/@madauth/web).
|
|
6
6
|
|
|
7
7
|
## Run it
|
|
8
8
|
|
|
9
|
-
With
|
|
9
|
+
With Node.js 22.13 or newer:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
|
|
12
|
+
npm install @madauth/server
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
```bash
|
|
16
|
+
npx @madauth/server init
|
|
17
|
+
```
|
|
16
18
|
|
|
17
19
|
```bash
|
|
18
|
-
npx @madauth/server
|
|
20
|
+
npx @madauth/server start --env-file .env
|
|
19
21
|
```
|
|
20
22
|
|
|
23
|
+
`init` asks a few questions and writes the configuration to `.env`, with a new signing key, then prints the next steps. `start` runs the server on port 8787. See [Getting started](https://github.com/inouiw/madAuth/blob/main/docs/getting-started.md).
|
|
24
|
+
|
|
25
|
+
With Docker:
|
|
26
|
+
|
|
21
27
|
```bash
|
|
22
|
-
|
|
28
|
+
docker run --rm -p 8787:8787 --env-file .env -v madauth-data:/data ghcr.io/inouiw/madauth-server
|
|
23
29
|
```
|
|
24
30
|
|
|
31
|
+
Or inside your own Node server:
|
|
32
|
+
|
|
25
33
|
```ts
|
|
26
34
|
import { createApp, loadConfig } from '@madauth/server';
|
|
27
35
|
|
|
@@ -38,12 +46,14 @@ The package also contains self-contained bundles for AWS Lambda (`dist/standalon
|
|
|
38
46
|
| `@madauth/server/verify` | verify a madAuth session in your own backend |
|
|
39
47
|
| `@madauth/server/webhook` | verify webhook calls in your receiver |
|
|
40
48
|
| `@madauth/server/sqlite` | the SQLite store |
|
|
49
|
+
| `@madauth/server/dynamodb` | the Amazon DynamoDB store |
|
|
41
50
|
| `@madauth/server/node`, `/lambda`, `/azure` | hosting entry points |
|
|
42
51
|
| `@madauth/server/testing` | a contract test suite for custom store adapters |
|
|
43
52
|
|
|
44
53
|
## Documentation
|
|
45
54
|
|
|
46
|
-
|
|
55
|
+
- [Getting started](https://github.com/inouiw/madAuth/blob/main/docs/getting-started.md): set up madAuth on your machine, step by step.
|
|
56
|
+
- [Running the madAuth server](https://github.com/inouiw/madAuth/blob/main/docs/server.md): the configuration, Google Cloud Console setup, webhooks, hosting and custom store adapters.
|
|
47
57
|
|
|
48
58
|
## License
|
|
49
59
|
|
package/dist/bin.js
CHANGED
|
@@ -1,46 +1,105 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
createTablesSql
|
|
4
|
-
|
|
3
|
+
createTablesSql,
|
|
4
|
+
upgradeTablesSql
|
|
5
|
+
} from "./chunk-CNL562LZ.js";
|
|
5
6
|
import {
|
|
6
|
-
|
|
7
|
+
madauthSchema
|
|
8
|
+
} from "./chunk-XNBWZZHT.js";
|
|
9
|
+
import {
|
|
10
|
+
Roles,
|
|
7
11
|
Users,
|
|
8
12
|
checkPasswordPolicy,
|
|
9
13
|
hashPassword,
|
|
10
14
|
isValidEmail,
|
|
15
|
+
loadConfig,
|
|
16
|
+
loadStore,
|
|
11
17
|
loadUserStoreConfig,
|
|
12
|
-
normalizeEmail
|
|
13
|
-
|
|
18
|
+
normalizeEmail,
|
|
19
|
+
parseRoles
|
|
20
|
+
} from "./chunk-5CKEMC6M.js";
|
|
14
21
|
import {
|
|
15
22
|
generateSigningKey
|
|
16
23
|
} from "./chunk-JDQLSL36.js";
|
|
17
24
|
import {
|
|
25
|
+
DEV_WEBHOOK_RECEIVER_URL,
|
|
18
26
|
generateWebhookSecret
|
|
19
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-6EJDVZCD.js";
|
|
20
28
|
|
|
21
29
|
// src/cli.ts
|
|
30
|
+
import { existsSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
22
31
|
import { createInterface } from "readline";
|
|
23
|
-
|
|
32
|
+
import { parseArgs, parseEnv } from "util";
|
|
33
|
+
var PORT = 8787;
|
|
34
|
+
var usage = `Usage: npx @madauth/server <command> [--env-file <path>]
|
|
24
35
|
|
|
25
36
|
Commands:
|
|
37
|
+
init Ask a few questions, write the configuration to .env and print the next steps.
|
|
38
|
+
An answer given as an option is not asked for: --app-url <url>,
|
|
39
|
+
--google-client-id <id>, --google-client-secret <secret>, --password or
|
|
40
|
+
--no-password, --database <url>, --webhook-url <url>. --yes takes the
|
|
41
|
+
default for the rest. --out <path> writes another file, --force overwrites.
|
|
42
|
+
start Start the server on PORT (default ${PORT})
|
|
26
43
|
generate-key Print a new private ES256 key to use as MADAUTH_SIGNING_KEY
|
|
27
44
|
generate-webhook-secret Print a new secret to use as WEBHOOK_SECRET (madAuth and your receiver)
|
|
28
45
|
create-user <email> Create a user with a password (asks for it), already verified.
|
|
29
46
|
Uses DATABASE_URL and PASSWORD_MIN_LENGTH from the environment.
|
|
30
|
-
|
|
31
|
-
|
|
47
|
+
set-roles <email> [role...] Set the roles of an e-mail address, e.g. to make the first admin:
|
|
48
|
+
set-roles you@example.com admin. Without roles, removes them all.
|
|
49
|
+
Uses DATABASE_URL from the environment.
|
|
50
|
+
get-roles <email> Print the roles of an e-mail address.
|
|
51
|
+
schema [--dialect <name>] [--from <version>]
|
|
52
|
+
Print the SQL that creates madAuth's tables for a custom store adapter.
|
|
53
|
+
Dialects: postgres (default), mysql, sqlite. With --from, print only the
|
|
54
|
+
changes since that schema version, to upgrade existing tables.
|
|
55
|
+
|
|
56
|
+
Options:
|
|
57
|
+
--env-file <path> Read environment variables from this file, for any command. A variable
|
|
58
|
+
that is already set in the environment wins.`;
|
|
59
|
+
var piped;
|
|
60
|
+
async function readPipedLine() {
|
|
61
|
+
if (!piped) {
|
|
62
|
+
const reader = createInterface({ input: process.stdin, terminal: false });
|
|
63
|
+
const input = piped = { reader, lines: reader[Symbol.asyncIterator](), ended: false };
|
|
64
|
+
reader.once("close", () => input.ended = true);
|
|
65
|
+
}
|
|
66
|
+
if (!piped.ended) piped.reader.resume();
|
|
67
|
+
const next = await piped.lines.next();
|
|
68
|
+
if (!piped.ended) piped.reader.pause();
|
|
69
|
+
return next.done ? "" : next.value;
|
|
70
|
+
}
|
|
32
71
|
var terminalIo = {
|
|
72
|
+
async ask(question, defaultValue = "") {
|
|
73
|
+
const prompt = `${question}${defaultValue ? ` [${defaultValue}]` : ""}: `;
|
|
74
|
+
if (!process.stdin.isTTY) {
|
|
75
|
+
process.stdout.write(prompt);
|
|
76
|
+
const line = await readPipedLine();
|
|
77
|
+
process.stdout.write("\n");
|
|
78
|
+
return line.trim() || defaultValue;
|
|
79
|
+
}
|
|
80
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
81
|
+
return new Promise((resolve, reject) => {
|
|
82
|
+
let answered = false;
|
|
83
|
+
rl.on("SIGINT", () => rl.close());
|
|
84
|
+
rl.on("close", () => {
|
|
85
|
+
if (answered) return;
|
|
86
|
+
process.stdout.write("\n");
|
|
87
|
+
reject(new Error("Cancelled."));
|
|
88
|
+
});
|
|
89
|
+
rl.question(prompt, (answer) => {
|
|
90
|
+
answered = true;
|
|
91
|
+
rl.close();
|
|
92
|
+
resolve(answer.trim() || defaultValue);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
},
|
|
33
96
|
async askSecret(question) {
|
|
34
97
|
process.stdout.write(question);
|
|
35
98
|
const stdin = process.stdin;
|
|
36
99
|
if (!stdin.isTTY) {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
process.stdout.write("\n");
|
|
41
|
-
return line;
|
|
42
|
-
}
|
|
43
|
-
return "";
|
|
100
|
+
const line = await readPipedLine();
|
|
101
|
+
process.stdout.write("\n");
|
|
102
|
+
return line;
|
|
44
103
|
}
|
|
45
104
|
stdin.setRawMode(true);
|
|
46
105
|
stdin.setEncoding("utf8");
|
|
@@ -72,9 +131,61 @@ var terminalIo = {
|
|
|
72
131
|
});
|
|
73
132
|
}
|
|
74
133
|
};
|
|
134
|
+
function parseCliArgs(args) {
|
|
135
|
+
return parseArgs({
|
|
136
|
+
args,
|
|
137
|
+
options: {
|
|
138
|
+
"env-file": { type: "string" },
|
|
139
|
+
dialect: { type: "string" },
|
|
140
|
+
// schema: print only the changes since this schema version.
|
|
141
|
+
from: { type: "string" },
|
|
142
|
+
// init: an answer for each question, then where and how to write the file.
|
|
143
|
+
"app-url": { type: "string" },
|
|
144
|
+
"google-client-id": { type: "string" },
|
|
145
|
+
"google-client-secret": { type: "string" },
|
|
146
|
+
password: { type: "boolean" },
|
|
147
|
+
database: { type: "string" },
|
|
148
|
+
"webhook-url": { type: "string" },
|
|
149
|
+
yes: { type: "boolean" },
|
|
150
|
+
out: { type: "string" },
|
|
151
|
+
force: { type: "boolean" }
|
|
152
|
+
},
|
|
153
|
+
allowPositionals: true,
|
|
154
|
+
// --no-password
|
|
155
|
+
allowNegative: true
|
|
156
|
+
});
|
|
157
|
+
}
|
|
75
158
|
async function runCli(args, io = terminalIo, env = process.env) {
|
|
76
|
-
|
|
159
|
+
let parsed;
|
|
77
160
|
try {
|
|
161
|
+
parsed = parseCliArgs(args);
|
|
162
|
+
} catch (e) {
|
|
163
|
+
return { output: `${e.message}
|
|
164
|
+
|
|
165
|
+
${usage}`, exitCode: 1 };
|
|
166
|
+
}
|
|
167
|
+
const { values: options, positionals } = parsed;
|
|
168
|
+
const [command, ...rest] = positionals;
|
|
169
|
+
const maxArguments = command === "set-roles" ? Infinity : command === "create-user" || command === "get-roles" ? 1 : 0;
|
|
170
|
+
if (rest.length > maxArguments) {
|
|
171
|
+
return { output: `Unexpected argument: ${rest.at(-1)}
|
|
172
|
+
|
|
173
|
+
${usage}`, exitCode: 1 };
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
const envFile = options["env-file"];
|
|
177
|
+
if (envFile !== void 0) {
|
|
178
|
+
if (!existsSync(envFile)) return { output: `--env-file: ${envFile} does not exist.`, exitCode: 1 };
|
|
179
|
+
env = withEnvFile(envFile, env);
|
|
180
|
+
}
|
|
181
|
+
if (command === "init") {
|
|
182
|
+
return await init(options, io);
|
|
183
|
+
}
|
|
184
|
+
if (command === "start") {
|
|
185
|
+
const { start } = await import("./entry/node.js");
|
|
186
|
+
await start({ env });
|
|
187
|
+
return { output: "", exitCode: 0 };
|
|
188
|
+
}
|
|
78
189
|
if (command === "generate-key") {
|
|
79
190
|
return { output: JSON.stringify(await generateSigningKey()), exitCode: 0 };
|
|
80
191
|
}
|
|
@@ -82,19 +193,170 @@ async function runCli(args, io = terminalIo, env = process.env) {
|
|
|
82
193
|
return { output: generateWebhookSecret(), exitCode: 0 };
|
|
83
194
|
}
|
|
84
195
|
if (command === "schema") {
|
|
85
|
-
const
|
|
86
|
-
const dialect = i >= 0 ? rest[i + 1] : "postgres";
|
|
196
|
+
const dialect = options.dialect ?? "postgres";
|
|
87
197
|
if (!["postgres", "mysql", "sqlite"].includes(dialect)) return { output: usage, exitCode: 1 };
|
|
88
|
-
return { output: createTablesSql(dialect), exitCode: 0 };
|
|
198
|
+
if (options.from === void 0) return { output: createTablesSql(dialect), exitCode: 0 };
|
|
199
|
+
const from = Number(options.from);
|
|
200
|
+
if (!Number.isInteger(from) || from < 1 || from > madauthSchema.version) return { output: usage, exitCode: 1 };
|
|
201
|
+
return { output: upgradeTablesSql(dialect, from) || "-- The tables are up to date.", exitCode: 0 };
|
|
89
202
|
}
|
|
90
203
|
if (command === "create-user") {
|
|
91
204
|
return await createUser(rest[0], io, env);
|
|
92
205
|
}
|
|
206
|
+
if (command === "set-roles" || command === "get-roles") {
|
|
207
|
+
return await roles(command, rest[0], rest.slice(1), env);
|
|
208
|
+
}
|
|
93
209
|
} catch (e) {
|
|
94
|
-
return { output: e instanceof
|
|
210
|
+
return { output: e instanceof Error ? e.message : String(e), exitCode: 1 };
|
|
95
211
|
}
|
|
96
212
|
return { output: usage, exitCode: command ? 1 : 0 };
|
|
97
213
|
}
|
|
214
|
+
function withEnvFile(path, env) {
|
|
215
|
+
const merged = parseEnv(readFileSync(path, "utf8"));
|
|
216
|
+
for (const [name, value] of Object.entries(env)) {
|
|
217
|
+
if (value !== void 0) merged[name] = value;
|
|
218
|
+
}
|
|
219
|
+
return merged;
|
|
220
|
+
}
|
|
221
|
+
function httpOrigin(value) {
|
|
222
|
+
const url = URL.canParse(value) ? new URL(value) : void 0;
|
|
223
|
+
return url && (url.protocol === "http:" || url.protocol === "https:") ? url.origin : void 0;
|
|
224
|
+
}
|
|
225
|
+
async function init(options, io) {
|
|
226
|
+
const fail = (output2) => ({ output: output2, exitCode: 1 });
|
|
227
|
+
const file = options.out ?? ".env";
|
|
228
|
+
if (existsSync(file) && !options.force) {
|
|
229
|
+
return fail(`${file} already exists. Use --force to overwrite it, or --out <path> to write another file.`);
|
|
230
|
+
}
|
|
231
|
+
const answer = async (given, question, defaultValue = "") => (given ?? (options.yes ? defaultValue : await io.ask(question, defaultValue))).trim();
|
|
232
|
+
const appUrl = await answer(options["app-url"], "App URL, the address your users open", "http://localhost:5173");
|
|
233
|
+
const origin = httpOrigin(appUrl);
|
|
234
|
+
if (!origin) return fail(`The app URL must be an http(s) URL, e.g. https://app.example.com, but is "${appUrl}".`);
|
|
235
|
+
const clientId = await answer(options["google-client-id"], "Google client ID (empty: no Google sign-in)");
|
|
236
|
+
const askClientSecret = () => options.yes ? "" : io.askSecret("Google client secret (empty: no redirect flow): ");
|
|
237
|
+
if (!clientId && options["google-client-secret"] !== void 0) {
|
|
238
|
+
return fail("--google-client-secret needs a Google client ID.");
|
|
239
|
+
}
|
|
240
|
+
const clientSecret = clientId ? (options["google-client-secret"] ?? await askClientSecret()).trim() : "";
|
|
241
|
+
const password = options.password ?? /^y/i.test(await answer(void 0, "E-mail & password sign-in? (yes/no)", "yes"));
|
|
242
|
+
if (!password && (options.database !== void 0 || options["webhook-url"] !== void 0)) {
|
|
243
|
+
return fail("--database and --webhook-url are for e-mail & password sign-in, which is turned off.");
|
|
244
|
+
}
|
|
245
|
+
if (!clientId && !password) {
|
|
246
|
+
return fail("No sign-in method is chosen. Give a Google client ID, turn on e-mail & password sign-in, or both.");
|
|
247
|
+
}
|
|
248
|
+
const database = password ? await answer(options.database, "Database for the users", "sqlite:./madauth.db") : "";
|
|
249
|
+
if (password && !/^(sqlite|dynamodb):./.test(database)) {
|
|
250
|
+
return fail(`The database must be sqlite:<path> or dynamodb:<table> but is "${database}".`);
|
|
251
|
+
}
|
|
252
|
+
const webhookUrl = password ? await answer(options["webhook-url"], "Webhook URL, where madAuth hands over its e-mails", "http://localhost:8790/webhook") : "";
|
|
253
|
+
const variable = (name, value, comment) => `# ${comment}
|
|
254
|
+
${name}=${value}
|
|
255
|
+
`;
|
|
256
|
+
const content = [
|
|
257
|
+
"# Configuration of the madAuth server, written by npx @madauth/server init. It holds secrets: never commit it.\n",
|
|
258
|
+
"# All settings: https://github.com/inouiw/madAuth/blob/main/docs/server.md#configuration\n\n",
|
|
259
|
+
variable(
|
|
260
|
+
"MADAUTH_ISSUER",
|
|
261
|
+
origin,
|
|
262
|
+
"Public address of the madAuth server: the origin of your app, which proxies /auth and /.well-known to it."
|
|
263
|
+
),
|
|
264
|
+
// In single quotes, as JSON is full of double quotes. Node's parser removes them; Docker's --env-file
|
|
265
|
+
// keeps them, and the server removes them then.
|
|
266
|
+
variable(
|
|
267
|
+
"MADAUTH_SIGNING_KEY",
|
|
268
|
+
`'${JSON.stringify(await generateSigningKey())}'`,
|
|
269
|
+
"Private key that signs the sessions. Keep it secret, and use the same one on every instance."
|
|
270
|
+
),
|
|
271
|
+
variable("ALLOWED_ORIGINS", origin, "Origins of your apps, comma-separated. Only these may call the server."),
|
|
272
|
+
clientId && variable("GOOGLE_CLIENT_ID", clientId, "Google sign-in: your OAuth client ID, of the type Web application."),
|
|
273
|
+
clientSecret && variable("GOOGLE_CLIENT_SECRET", clientSecret, "Client secret of the same client. It enables the redirect flow."),
|
|
274
|
+
password && variable("DATABASE_URL", database, "E-mail & password sign-in: where the users are stored."),
|
|
275
|
+
password && variable("WEBHOOK_URL", webhookUrl, "Your webhook receiver. madAuth hands its e-mails to it."),
|
|
276
|
+
password && variable("WEBHOOK_SECRET", generateWebhookSecret(), "Signs every webhook call. Your receiver needs the same one."),
|
|
277
|
+
password && variable(
|
|
278
|
+
"WEBHOOK_EVENTS",
|
|
279
|
+
"email.verify,email.reset,email.already_registered",
|
|
280
|
+
"The types your webhook receiver handles; only these are sent. Add e.g. signup.before or user.created."
|
|
281
|
+
),
|
|
282
|
+
variable("PORT", String(PORT), "Port the server listens on.")
|
|
283
|
+
].filter(Boolean).join("");
|
|
284
|
+
await loadConfig({ ...parseEnv(content), DATABASE_URL: void 0 }, password ? { store: {} } : {});
|
|
285
|
+
if (options.force) rmSync(file, { force: true });
|
|
286
|
+
writeFileSync(file, content, { mode: 384, flag: "wx" });
|
|
287
|
+
const google = clientSecret ? "GoogleRedirect" : clientId ? "GoogleFedcm" : void 0;
|
|
288
|
+
return { output: `Wrote ${file}.
|
|
289
|
+
|
|
290
|
+
Next steps:
|
|
291
|
+
|
|
292
|
+
${nextSteps(file, origin, google, password)}`, exitCode: 0 };
|
|
293
|
+
}
|
|
294
|
+
function nextSteps(file, origin, google, password) {
|
|
295
|
+
const server = `http://localhost:${PORT}`;
|
|
296
|
+
const envFile = /[^\w./-]/.test(file) ? `'${file.replaceAll("'", `'\\''`)}'` : file;
|
|
297
|
+
const providers = [google, password && "Password"].filter(Boolean);
|
|
298
|
+
const googleOrigins = /* @__PURE__ */ new Set([origin, ...new URL(origin).hostname === "localhost" ? ["http://localhost"] : []]);
|
|
299
|
+
const steps = [
|
|
300
|
+
["Start the madAuth server:", "", ` npx @madauth/server start --env-file ${envFile}`],
|
|
301
|
+
password && [
|
|
302
|
+
"madAuth hands its e-mails to your webhook receiver. For development, start the development receiver,",
|
|
303
|
+
`which prints them, with the same WEBHOOK_URL and WEBHOOK_SECRET as in ${file}:`,
|
|
304
|
+
"",
|
|
305
|
+
` ${DEV_WEBHOOK_RECEIVER_URL}`
|
|
306
|
+
],
|
|
307
|
+
[
|
|
308
|
+
"Proxy /auth and /.well-known from your app to the server, so that its cookies are first-party.",
|
|
309
|
+
"With Vite, in vite.config.ts:",
|
|
310
|
+
"",
|
|
311
|
+
" server: {",
|
|
312
|
+
" proxy: {",
|
|
313
|
+
` '/auth': '${server}',`,
|
|
314
|
+
` '/.well-known': '${server}',`,
|
|
315
|
+
" },",
|
|
316
|
+
" },"
|
|
317
|
+
],
|
|
318
|
+
[
|
|
319
|
+
"Sign users in from your app (npm install @madauth/web):",
|
|
320
|
+
"",
|
|
321
|
+
` import { Madauth, ${providers.join(", ")} } from '@madauth/web';`,
|
|
322
|
+
"",
|
|
323
|
+
` Madauth.initialize({ providers: [${providers.map((provider) => `new ${provider}()`).join(", ")}] });`,
|
|
324
|
+
" Madauth.onAuthStateChanged((user) => console.log(user)); // the user, or null",
|
|
325
|
+
" signInButton.onclick = () => Madauth.signIn();"
|
|
326
|
+
],
|
|
327
|
+
google && [
|
|
328
|
+
"In the Google Cloud Console (https://console.cloud.google.com/apis/credentials), add to your OAuth client:",
|
|
329
|
+
"",
|
|
330
|
+
` Authorized JavaScript origins: ${[...googleOrigins].join(" and ")}`,
|
|
331
|
+
...google === "GoogleRedirect" ? [` Authorized redirect URIs: ${origin}/auth/google/callback`] : []
|
|
332
|
+
],
|
|
333
|
+
[
|
|
334
|
+
`Never commit ${file}: it holds the signing key and your secrets. Production needs an https app URL`,
|
|
335
|
+
"and a signing key of its own: run init again for it."
|
|
336
|
+
]
|
|
337
|
+
].filter((step) => !!step);
|
|
338
|
+
return steps.map((lines, i) => `${i + 1}. ${lines.map((line, j) => j && line ? ` ${line}` : line).join("\n")}`).join("\n\n");
|
|
339
|
+
}
|
|
340
|
+
async function roles(command, email, names, env) {
|
|
341
|
+
if (!isValidEmail(email)) return { output: `"${email ?? ""}" is not an e-mail address.
|
|
342
|
+
|
|
343
|
+
${usage}`, exitCode: 1 };
|
|
344
|
+
const store = await loadStore(env);
|
|
345
|
+
const address = normalizeEmail(email);
|
|
346
|
+
const assignments = new Roles(store);
|
|
347
|
+
if (command === "set-roles") {
|
|
348
|
+
const parsed = parseRoles(names);
|
|
349
|
+
if (!parsed) {
|
|
350
|
+
return {
|
|
351
|
+
output: 'Role names consist of lower-case letters, digits, "-" and "_", and start with a letter, e.g. admin.',
|
|
352
|
+
exitCode: 1
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
await assignments.set(address, parsed, null);
|
|
356
|
+
}
|
|
357
|
+
const current = await assignments.get(address);
|
|
358
|
+
return { output: current.length ? `${address}: ${current.join(" ")}` : `${address} has no roles.`, exitCode: 0 };
|
|
359
|
+
}
|
|
98
360
|
async function createUser(email, io, env) {
|
|
99
361
|
if (!isValidEmail(email)) return { output: `"${email ?? ""}" is not an e-mail address.
|
|
100
362
|
|
|
@@ -117,5 +379,5 @@ ${usage}`, exitCode: 1 };
|
|
|
117
379
|
|
|
118
380
|
// src/bin.ts
|
|
119
381
|
var { output, exitCode } = await runCli(process.argv.slice(2));
|
|
120
|
-
(exitCode ? console.error : console.log)(output);
|
|
382
|
+
if (output) (exitCode ? console.error : console.log)(output);
|
|
121
383
|
process.exitCode = exitCode;
|