idp-admin 0.1.0 → 0.1.2

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 CHANGED
@@ -6,7 +6,7 @@ Apps that sign people in through an identity provider (Cognito, Keycloak, Entra
6
6
 
7
7
  idp-admin is that layer, written once.
8
8
 
9
- > **Status:** early development. Not yet published.
9
+ > **Status:** 0.x, so the API may still change between minor versions. The adapters are published and usable directly; `IdpAdmin`, the class that adds the shared rules on top of them (including the access-reduced hook below), isn't built yet. See the [quick start](#quick-start) and the [reference](docs/index.md).
10
10
 
11
11
  ## Why it exists
12
12
 
@@ -35,9 +35,86 @@ This package came out of exactly that situation: two production apps, each with
35
35
  - **Not a lowest common denominator.** Providers differ, and some features don't exist everywhere. Adapters declare what they support, and you can always reach the provider's own SDK for anything the shared interface doesn't cover.
36
36
  - **Not a UI.** It's the server-side layer an admin screen, a background job or a seed script would call.
37
37
 
38
+ ## Quick start
39
+
40
+ ```sh
41
+ npm install idp-admin
42
+ npm install @aws-sdk/client-cognito-identity-provider # only for Cognito
43
+ ```
44
+
45
+ Pick an adapter for your provider. Each one's setup (credentials, permissions, realm or pool settings) is in its doc: [Cognito](docs/cognito.md), [Keycloak](docs/keycloak.md), [fake](docs/fake.md).
46
+
47
+ ```ts
48
+ import type { IdpAdapter } from "idp-admin"
49
+ import { CognitoAdapter } from "idp-admin/cognito"
50
+ import { KeycloakAdapter } from "idp-admin/keycloak"
51
+
52
+ // Cognito in production, Keycloak in a container locally: the rest of the app doesn't know which
53
+ const idp: IdpAdapter<"company"> = process.env.COGNITO_USER_POOL_ID
54
+ ? new CognitoAdapter({
55
+ userPoolId: process.env.COGNITO_USER_POOL_ID,
56
+ region: "us-east-1",
57
+ attributes: { company: "custom:companyName" },
58
+ })
59
+ : new KeycloakAdapter({
60
+ issuer: "http://localhost:8080/realms/acme",
61
+ clientId: "acme-admin",
62
+ clientSecret: process.env.KEYCLOAK_ADMIN_SECRET!,
63
+ attributes: { company: "companyName" },
64
+ })
65
+ ```
66
+
67
+ `attributes` maps the names your app uses to the provider's own, and only those names compile.
68
+
69
+ ```ts
70
+ import { UserExistsError } from "idp-admin"
71
+
72
+ // create a user who must set their own password at first sign-in; send them the temporary one yourself
73
+ try{
74
+ const user = await idp.createUser({
75
+ email: "dana@example.com",
76
+ name: "Dana Lee",
77
+ attributes: { company: "Acme" },
78
+ temporaryPassword: "Temp-pass-1!",
79
+ })
80
+ if(!(await idp.groupExists("admins"))) await idp.createGroup("admins")
81
+ await idp.addToGroup(user.id, "admins")
82
+ }catch(error){
83
+ if(error instanceof UserExistsError) console.log("already invited")
84
+ else throw error
85
+ }
86
+
87
+ // take access away, and end the sessions the provider already issued
88
+ const dana = await idp.findUserByEmail("dana@example.com")
89
+ if(dana){
90
+ await idp.setEnabled(dana.id, false)
91
+ await idp.signOutEverywhere(dana.id)
92
+ }
93
+
94
+ // list everyone, a page at a time
95
+ let cursor: string | null = null
96
+ do{
97
+ const page = await idp.listUsersPage(cursor)
98
+ for(const user of page.items) console.log(user.email, user.status, user.enabled)
99
+ cursor = page.nextCursor
100
+ }while(cursor)
101
+ ```
102
+
103
+ A user's `id` is the `sub` in their sign-in tokens, so it matches what your auth library gives you. Pass emails lowercased and trimmed, and `findUserByEmail` returns `null` while `getUser` throws `UserNotFoundError`. The full list of methods, types and errors is in [docs/index.md](docs/index.md).
104
+
105
+ In tests, use the fake instead. It keeps everything in memory and records what a real provider would have done:
106
+
107
+ ```ts
108
+ import { FakeAdapter } from "idp-admin/fake"
109
+
110
+ const fake = new FakeAdapter({ attributes: ["company"] })
111
+ // ...run the code under test with `fake` as its IdpAdapter, then:
112
+ expect(fake.signOuts).toContain(userId)
113
+ ```
114
+
38
115
  ## Providers
39
116
 
40
- The first release includes Cognito, Keycloak and the fake provider: the ones we run in production and can test properly.
117
+ Cognito, Keycloak and the fake provider are included today: the ones we run in production and can test properly.
41
118
 
42
119
  The interface is designed so that other providers (Entra ID, Auth0, Okta, Zitadel, Authentik and more) can be added through the same contract. Each one is contributed here and must pass the shared test suite.
43
120
 
package/docs/fake.md CHANGED
@@ -22,7 +22,7 @@ const fake = new FakeAdapter({ attributes: ["company"] })
22
22
  ## Behaviour
23
23
 
24
24
  - **Ids** are UUID v4s from `crypto.randomUUID()`.
25
- - **Emails** are matched exactly. They arrive already lowercased (`IdpAdmin` normalises them), so the fake doesn't lowercase again.
25
+ - **Emails** are matched exactly. The adapter contract expects them already lowercased and trimmed, so the fake doesn't lowercase again; pass them that way when calling it directly.
26
26
  - **Status:**
27
27
  - a user from `createUser` or `createUserWithInvite` starts as `password_change_required`
28
28
  - `setPassword` with `temporary: true` sets `password_change_required`, and with `temporary: false` sets `active`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "idp-admin",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "One user-management API for any identity provider.",
5
5
  "keywords": [
6
6
  "identity-provider",