@jskit-ai/oidc-issuer-core 0.0.0-stage → 0.1.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 +37 -2
- package/bin/jskit-oidc-issuer.js +67 -0
- package/data/common-passwords.txt +46483 -0
- package/migrations/oidc_issuer_initial.cjs +106 -0
- package/package.json +119 -4
- package/patterns/accounts-hub/PATTERN.md +97 -0
- package/patterns/accounts-hub/example/.env.example +10 -0
- package/patterns/accounts-hub/example/package.json +8 -0
- package/patterns/accounts-hub/example/server/hubPaths.js +7 -0
- package/src/server/accounts.js +245 -0
- package/src/server/adapter.js +78 -0
- package/src/server/clients.js +192 -0
- package/src/server/config.js +153 -0
- package/src/server/crypto.js +102 -0
- package/src/server/emailTokens.js +41 -0
- package/src/server/google.js +88 -0
- package/src/server/http.js +71 -0
- package/src/server/hubRoutes.js +627 -0
- package/src/server/hubSessions.js +88 -0
- package/src/server/importAccounts.js +84 -0
- package/src/server/index.js +5 -0
- package/src/server/issuer.js +267 -0
- package/src/server/mailer.js +56 -0
- package/src/server/oidcProvider.js +179 -0
- package/src/server/previewIdentity.js +98 -0
- package/src/server/providers/OidcIssuerProvider.js +30 -0
- package/src/server/rateLimits.js +43 -0
- package/src/server/signingKeys.js +80 -0
package/README.md
CHANGED
|
@@ -1,3 +1,38 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @jskit-ai/oidc-issuer-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An accounts hub for a JSKIT app: one sign-in for all of an owner's apps. The
|
|
4
|
+
hub is an OpenID Connect issuer that runs as its own project on a domain of its
|
|
5
|
+
own. People create one account there (email and password, optional "Continue
|
|
6
|
+
with Google" with the owner's own Google client); the owner approves each app
|
|
7
|
+
that wants to use those accounts.
|
|
8
|
+
|
|
9
|
+
Start from the **JSKIT · Accounts hub** template (seed-jskit branch
|
|
10
|
+
`accounts-hub`): it has the sign-in, account and admin pages, the tests and a
|
|
11
|
+
quick start. To build a hub by hand, follow the `auth/accounts-hub` pattern.
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install @jskit-ai/oidc-issuer-core @jskit-ai/database-runtime-mysql
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The settings that matter, in the order a new hub needs them:
|
|
18
|
+
|
|
19
|
+
| Name | Meaning |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `OIDC_HUB_OWNER_EMAIL` | The owner's email. That account, once confirmed, opens Apps, People and Setup. |
|
|
22
|
+
| `OIDC_HUB_REGISTRATION_TOKEN` | At least 24 random characters. Every app presents it to register. |
|
|
23
|
+
| `OIDC_HUB_SECRET` | At least 32 random bytes; required in production, never changed once set. |
|
|
24
|
+
| `APP_PUBLIC_URL` | The hub's https address, which is its issuer. Required in production. |
|
|
25
|
+
| `OIDC_HUB_EMAIL_FROM` with `RESEND_API_KEY` or `SMTP_URL` | The owner's own sender. Required in production. |
|
|
26
|
+
| `OIDC_HUB_DEV_ORIGIN_PATTERNS` | Optional `https://` globs of development addresses approved automatically. |
|
|
27
|
+
|
|
28
|
+
A missing or wrong setting stops the hub with one sentence that names it.
|
|
29
|
+
|
|
30
|
+
Apps connect with `@jskit-ai/auth-provider-oidc-core` and two values: the hub's
|
|
31
|
+
address and its registration token.
|
|
32
|
+
|
|
33
|
+
Commands, run from the hub project: `npx jskit-oidc-issuer keys rotate` and
|
|
34
|
+
`npx jskit-oidc-issuer import-accounts --in accounts.jsonl [--apply]` (a dry run
|
|
35
|
+
unless `--apply`).
|
|
36
|
+
|
|
37
|
+
Guides: "Run an accounts hub" and "Sign in through an OIDC issuer" in the JSKIT
|
|
38
|
+
guide (`packages/agent-docs/site/guide/app-setup/`).
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createKnexMigrationConfigFromApp } from "@jskit-ai/database-runtime/server/knexMigrationConfig";
|
|
5
|
+
import createKnex from "knex";
|
|
6
|
+
import { resolveHubConfig } from "../src/server/config.js";
|
|
7
|
+
import { describeImport, importAccounts } from "../src/server/importAccounts.js";
|
|
8
|
+
import { runPreviewIdentityCommand } from "../src/server/previewIdentity.js";
|
|
9
|
+
import { createSigningKeyStore } from "../src/server/signingKeys.js";
|
|
10
|
+
|
|
11
|
+
const USAGE = `Usage:
|
|
12
|
+
jskit-oidc-issuer keys rotate
|
|
13
|
+
jskit-oidc-issuer import-accounts --in <file> [--apply]
|
|
14
|
+
jskit-oidc-issuer preview-identity
|
|
15
|
+
|
|
16
|
+
Run from the hub application's folder; it reads the same .env and database settings.`;
|
|
17
|
+
|
|
18
|
+
function loadDotEnv() {
|
|
19
|
+
try {
|
|
20
|
+
process.loadEnvFile?.(".env");
|
|
21
|
+
} catch (error) {
|
|
22
|
+
if (error?.code !== "ENOENT") throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function withKnex(work) {
|
|
27
|
+
const config = await createKnexMigrationConfigFromApp({ client: process.env.DB_CLIENT || "mysql2" });
|
|
28
|
+
const knex = createKnex({ client: config.client, connection: config.connection });
|
|
29
|
+
try {
|
|
30
|
+
return await work(knex);
|
|
31
|
+
} finally {
|
|
32
|
+
await knex.destroy();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function main(argv) {
|
|
37
|
+
const [command, ...rest] = argv;
|
|
38
|
+
if (command === "preview-identity") {
|
|
39
|
+
return runPreviewIdentityCommand();
|
|
40
|
+
}
|
|
41
|
+
loadDotEnv();
|
|
42
|
+
if (command === "keys" && rest[0] === "rotate") {
|
|
43
|
+
const config = resolveHubConfig(process.env, { appRoot: process.cwd() });
|
|
44
|
+
const kid = await withKnex((knex) => createSigningKeyStore(knex, { secret: config.secret }).rotate());
|
|
45
|
+
console.log(`New signing key ${kid} is active. The previous key keeps verifying for two hours. A running hub picks it up within a minute.`);
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
if (command === "import-accounts") {
|
|
49
|
+
const inIndex = rest.indexOf("--in");
|
|
50
|
+
const file = inIndex >= 0 ? rest[inIndex + 1] : "";
|
|
51
|
+
if (!file || !existsSync(path.resolve(file))) {
|
|
52
|
+
console.error("Name the exported accounts file with --in <file>.");
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
const apply = rest.includes("--apply");
|
|
56
|
+
const result = await withKnex((knex) => importAccounts({ knex, text: readFileSync(path.resolve(file), "utf8"), apply }));
|
|
57
|
+
console.log(describeImport(result, { apply }));
|
|
58
|
+
return result.conflicts.length > 0 ? 2 : 0;
|
|
59
|
+
}
|
|
60
|
+
console.error(USAGE);
|
|
61
|
+
return 1;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
process.exitCode = await main(process.argv.slice(2)).catch((error) => {
|
|
65
|
+
console.error(error?.message || String(error));
|
|
66
|
+
return 1;
|
|
67
|
+
});
|