everyapp 0.2.2 → 0.3.0-rc.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.
Files changed (128) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -77
  3. package/bin/everyapp.js +235 -0
  4. package/dist/access-LGEN3ABJ.js +1 -0
  5. package/dist/activity-RSD4D66F.js +12 -0
  6. package/dist/apps-4RCFXT74.js +13 -0
  7. package/dist/chunk-23HJ5I2A.js +2 -0
  8. package/dist/chunk-2Y5Q6A5K.js +6 -0
  9. package/dist/chunk-4JRIS6S6.js +1 -0
  10. package/dist/chunk-5PTNYZK6.js +4 -0
  11. package/dist/chunk-6OGVO7QK.js +6 -0
  12. package/dist/chunk-6PGUCTJ6.js +11 -0
  13. package/dist/chunk-AGTCKFRB.js +22 -0
  14. package/dist/chunk-BHMRY53V.js +2 -0
  15. package/dist/chunk-CB4U7H2G.js +4 -0
  16. package/dist/chunk-CCWBPAKH.js +4 -0
  17. package/dist/chunk-CZTEDWZ2.js +22 -0
  18. package/dist/chunk-CZXHKPRZ.js +2 -0
  19. package/dist/chunk-E3EPK27P.js +10 -0
  20. package/dist/chunk-EGEWRFVY.js +1 -0
  21. package/dist/chunk-ELK7QZ2M.js +1 -0
  22. package/dist/chunk-FJJSSVET.js +33 -0
  23. package/dist/chunk-GKBPYVP3.js +25 -0
  24. package/dist/chunk-H6QQMY7B.js +18 -0
  25. package/dist/chunk-H7PDL3X5.js +1 -0
  26. package/dist/chunk-HOUHG236.js +20 -0
  27. package/dist/chunk-IFCPALVC.js +46 -0
  28. package/dist/chunk-IUOBIB5M.js +30 -0
  29. package/dist/chunk-J35DDCNU.js +1 -0
  30. package/dist/chunk-J455FAEN.js +7 -0
  31. package/dist/chunk-JYKRUS5C.js +1 -0
  32. package/dist/chunk-LXYMDZER.js +1 -0
  33. package/dist/chunk-LYNBZ7RV.js +57 -0
  34. package/dist/chunk-M2QQ2UD7.js +132 -0
  35. package/dist/chunk-N4E5YE4X.js +5 -0
  36. package/dist/chunk-N5GQLGEC.js +2 -0
  37. package/dist/chunk-NFU4YFXB.js +1 -0
  38. package/dist/chunk-OM2HDWVP.js +1 -0
  39. package/dist/chunk-ONLNJXVQ.js +32 -0
  40. package/dist/chunk-OP4DYXYI.js +2 -0
  41. package/dist/chunk-PNLU5U3P.js +1 -0
  42. package/dist/chunk-PS7S74DG.js +6 -0
  43. package/dist/chunk-RMF4YGD6.js +98 -0
  44. package/dist/chunk-T2GD7RHS.js +10 -0
  45. package/dist/chunk-TLRM3P7V.js +15 -0
  46. package/dist/chunk-W3JCQHHU.js +1 -0
  47. package/dist/chunk-WOVS3GV5.js +10 -0
  48. package/dist/chunk-YHJZMJYZ.js +1 -0
  49. package/dist/chunk-ZECBN24L.js +15 -0
  50. package/dist/chunk-ZEXVHB22.js +2 -0
  51. package/dist/chunk-ZFCUH5PK.js +7 -0
  52. package/dist/configure-F7UZCJUG.js +8 -0
  53. package/dist/create-2E5HVWYK.js +22 -0
  54. package/dist/db-HEQVTDTM.js +9 -0
  55. package/dist/delete-XCSIQIRS.js +3 -0
  56. package/dist/deploy-3QOZEZHC.js +1 -0
  57. package/dist/dev-JL3ZP5BT.js +86 -0
  58. package/dist/email-RLZFUFUN.js +10 -0
  59. package/dist/events-2V5VRBWP.js +15 -0
  60. package/dist/groups-HDG5DVFY.js +8 -0
  61. package/dist/index.js +97 -1
  62. package/dist/install-Z5FYXUYM.js +1 -0
  63. package/dist/login-KHJBC64Z.js +1 -0
  64. package/dist/mcp-EFDUKJ5V.js +1 -0
  65. package/dist/provider-EOI5PTFH.js +13 -0
  66. package/dist/release-74J5ZIL3.js +1 -0
  67. package/dist/restore-FFGYV7NO.js +3 -0
  68. package/dist/secret-7FKKTLHB.js +1 -0
  69. package/dist/setup-PAKRCAFO.js +1 -0
  70. package/dist/signin-AP3D4XM4.js +12 -0
  71. package/dist/status-RMWNHOEK.js +4 -0
  72. package/dist/teardown-E4JNKAZJ.js +22 -0
  73. package/dist/update-DWVZFNPS.js +1 -0
  74. package/dist/workspace-CJC5I66L.js +21 -0
  75. package/package.json +19 -16
  76. package/skills/build-every-app/SKILL.md +199 -0
  77. package/skills/build-every-app/patterns.md +140 -0
  78. package/skills/everyapp/SKILL.md +292 -0
  79. package/skills/everyapp/mcp.md +350 -0
  80. package/skills/everyapp/security-review.md +61 -0
  81. package/skills/everyapp/setup/README.md +274 -0
  82. package/skills/everyapp/setup/access.md +178 -0
  83. package/skills/everyapp/setup/context.md +117 -0
  84. package/skills/everyapp/setup/dataforseo.md +287 -0
  85. package/skills/everyapp/setup/email.md +366 -0
  86. package/skills/everyapp/setup/gateway.md +243 -0
  87. package/skills/everyapp/setup/google.md +360 -0
  88. package/skills/everyapp/setup/microsoft-365.md +281 -0
  89. package/skills/everyapp/setup/openai.md +318 -0
  90. package/skills/everyapp/setup/openrouter.md +309 -0
  91. package/skills/everyapp/setup/providers.md +1253 -0
  92. package/skills/everyapp/setup/remote-mcp.md +199 -0
  93. package/skills/everyapp/setup/signin-apple.md +178 -0
  94. package/skills/everyapp/setup/signin-cloudflare.md +148 -0
  95. package/skills/everyapp/setup/signin-github.md +139 -0
  96. package/skills/everyapp/setup/signin-google.md +178 -0
  97. package/skills/everyapp/setup/signin-microsoft.md +183 -0
  98. package/skills/everyapp/setup/signin-oidc.md +163 -0
  99. package/skills/everyapp/setup/slack.md +344 -0
  100. package/skills/everyapp/setup/workers-ai.md +138 -0
  101. package/bin/__everyapp_bash_complete.js +0 -12
  102. package/dist/bash-complete.js +0 -3
  103. package/dist/chunk-7BOQJFI5.js +0 -8
  104. package/dist/chunk-7YVJFS4A.js +0 -1
  105. package/dist/chunk-BIXKPCDH.js +0 -2
  106. package/dist/chunk-DLGPLHBH.js +0 -47
  107. package/dist/chunk-FD6K2YA7.js +0 -9
  108. package/dist/chunk-GAJEQYJH.js +0 -39
  109. package/dist/chunk-O2KDOP5R.js +0 -13
  110. package/dist/chunk-OI2MBGBG.js +0 -4
  111. package/dist/chunk-POSDLD3G.js +0 -8
  112. package/dist/chunk-RB5D2R45.js +0 -1
  113. package/dist/chunk-U4LIQ5VR.js +0 -1
  114. package/dist/chunk-VEYSNPDH.js +0 -3
  115. package/dist/chunk-VP6SKW44.js +0 -7
  116. package/dist/chunk-WO3NE7ZU.js +0 -7
  117. package/dist/chunk-XTO2VTPY.js +0 -6
  118. package/dist/chunk-YJ2DKIZS.js +0 -1
  119. package/dist/create-DYQ3NBWJ.js +0 -22
  120. package/dist/deploy-PYTCMDGV.js +0 -7
  121. package/dist/deploy-RMAHOA4Z.js +0 -55
  122. package/dist/dev-SAE237FS.js +0 -0
  123. package/dist/generateConfig-46Z3ERF3.js +0 -1
  124. package/dist/login-B6EF3SMX.js +0 -1
  125. package/dist/logout-XMKZBLNV.js +0 -1
  126. package/dist/remoteD1Shell-A67JMO3H.js +0 -6
  127. package/dist/setupLocal-TZJUW5ES.js +0 -6
  128. package/gateway/every-app-gateway-build.tar.gz +0 -0
@@ -0,0 +1,139 @@
1
+ # Sign in with GitHub on the Gateway
2
+
3
+ Read [README.md](README.md) first: the rules every setup follows.
4
+
5
+ Puts **Continue with GitHub** on the Gateway's sign-in page, beside the
6
+ password. At the end the person has a GitHub OAuth App, its two settings
7
+ on the Gateway, and has signed in with GitHub once.
8
+ `pnpm everyapp signin show github --json` names this file as `setup`, and
9
+ the CLI prints `Guide: .claude/skills/everyapp/setup/signin-github.md`.
10
+
11
+ Who gets in doesn't change: GitHub only says who someone is, by their
12
+ primary address, and only when GitHub marks it verified. A member signs
13
+ in; someone with a pending invitation for that exact address joins with
14
+ the invited role; everyone else is refused ("You don't have an account on
15
+ this gateway.") and nothing is created. An existing password account
16
+ connects GitHub once, by signing in with the password when the page asks.
17
+ The rules: `docs/runbooks/sign-in-providers.md`, "Who gets in".
18
+
19
+ An **OAuth App**, not a GitHub App: the two live side by side under
20
+ Developer settings, and only an OAuth App fits here.
21
+
22
+ ## Settings
23
+
24
+ | Name | Required | Flag | What it holds |
25
+ | ----------------------------- | -------- | -------------------------------- | ---------------------------------------------- |
26
+ | `GITHUB_SIGNIN_CLIENT_ID` | required | `--client-id <id>` | the OAuth App's Client ID; not a secret |
27
+ | `GITHUB_SIGNIN_CLIENT_SECRET` | required | `--client-secret-from-env <VAR>` | a client secret generated on the OAuth App |
28
+
29
+ Scopes asked for: `read:user user:email`. The email is the person's
30
+ primary address from GitHub's emails API, counted only when verified.
31
+
32
+ ## Costs and plans
33
+
34
+ Free, on any GitHub account. An OAuth App can belong to the person's own
35
+ account or to an organization they administer (up to 100 per owner); an
36
+ organization owner keeps it from depending on one person.
37
+
38
+ ## Only a person can
39
+
40
+ - Sign in to GitHub and register the OAuth App.
41
+ - Generate the client secret and copy it off the screen.
42
+ - Type the secret at the hidden prompt of `pnpm everyapp signin add github`.
43
+ - Sign in with GitHub to check it.
44
+
45
+ ## Before you start
46
+
47
+ 1. `pnpm everyapp status --json`: `data.installed` and `data.claimed` true;
48
+ `data.gatewayUrl` gives `<gateway host>`. Not installed or no owner:
49
+ [gateway.md](gateway.md) first. `data.access.on` true: see Verify 4.
50
+ 2. `pnpm everyapp signin show github --json` (changes nothing; `Unknown
51
+ command signin`: `git pull` in the `every-app` folder). `data.status`
52
+ `off`: this file applies; `needs setup`: `data.missing` says what is
53
+ left (step 4); `on`: ask before replacing. `data.callbackUrl` is
54
+ `https://<gateway host>/api/auth/callback/github`.
55
+ 3. Ask whether the app should belong to their own account or to an
56
+ organization they administer.
57
+
58
+ ## Steps
59
+
60
+ 1. **You:** give the person `data.callbackUrl`.
61
+ 2. **Person:** open https://github.com/settings/developers (for an
62
+ organization: the organization's **Settings** → **Developer settings**
63
+ → **OAuth Apps**) → **New OAuth App** (**Register a new application**
64
+ when there is none yet). Application name (people see it, for example
65
+ `Every App`), Homepage URL `https://<gateway host>`, **Authorization
66
+ callback URL** the exact URL from step 1. Leave **Enable Device Flow**
67
+ off. **Register application**. The app's page shows the **Client ID**.
68
+ 3. **Person:** on that page, **Generate a new client secret** (GitHub may
69
+ ask for their password or 2FA again). The secret is shown once; keep
70
+ the page open for step 4.
71
+ 4. **Person:** in a second terminal in the `every-app` folder:
72
+ ```sh
73
+ pnpm everyapp signin add github
74
+ ```
75
+ It asks for the Client ID (shown as typed) and the client secret
76
+ (hidden: pasting works, nothing is echoed), and writes both in one new
77
+ Gateway Worker version, live on the next request. They tell you when
78
+ it's done.
79
+ Another way: in that terminal, the hidden read (bash or zsh:
80
+ `read -rs GITHUB_SIGNIN_CLIENT_SECRET && export GITHUB_SIGNIN_CLIENT_SECRET`;
81
+ fish: `read -s -x GITHUB_SIGNIN_CLIENT_SECRET`), paste, Enter, then
82
+ `pnpm everyapp signin add github --client-id <Client ID> --client-secret-from-env GITHUB_SIGNIN_CLIENT_SECRET --json`
83
+ and `unset GITHUB_SIGNIN_CLIENT_SECRET` (fish: `set -e GITHUB_SIGNIN_CLIENT_SECRET`).
84
+
85
+ ## Verify
86
+
87
+ 1. `pnpm everyapp signin list --json`: the `github` entry has `status:
88
+ "on"` and `missing` empty.
89
+ 2. The person's GitHub primary address (https://github.com/settings/emails,
90
+ marked Primary, not Unverified) must be a member's or have a pending
91
+ invitation: `pnpm everyapp workspace invite <email>` for someone new.
92
+ 3. **Person:** private window, `https://<gateway host>/sign-in` →
93
+ **Continue with GitHub** → **Authorize** on GitHub's page → the
94
+ Gateway, signed in.
95
+ 4. Behind Cloudflare Access: nothing else changes, but the GitHub primary
96
+ address must be the one Access knows the person by.
97
+ 5. Logs (README's Verify 4): `signin.config_problem` for a malformed or
98
+ missing setting.
99
+
100
+ ## If it fails
101
+
102
+ | error or symptom | meaning | what to do |
103
+ | --- | --- | --- |
104
+ | no **Continue with GitHub** button | a setting missing or malformed, or the version isn't live yet | `pnpm everyapp signin list --json`; logs' `signin.config_problem` |
105
+ | `redirect_uri_mismatch`: "The redirect_uri MUST match the registered callback URL for this application." | the Authorization callback URL isn't exactly `data.callbackUrl` | fix it on the OAuth App's page → **Update application** |
106
+ | "The sign-in provider hasn't verified this email address." | the primary address is unverified at GitHub | the person verifies it in GitHub's Emails settings, or makes a verified one primary |
107
+ | "You don't have an account on this gateway." | the primary address isn't a member's or invited | invite that address (people often sign up to GitHub with a personal one) |
108
+ | `incorrect_client_credentials` | the secret doesn't belong to this Client ID | Rotate |
109
+ | `SIGNIN_VALUE_REQUIRED` / `SECRET_EMPTY` | unattended, a required setting neither given nor set / the env variable empty in that shell | `data.rerun` with the flag / the hidden read again in the same terminal |
110
+ | `SIGNIN_FLAG_NOT_APPLICABLE` | `--tenant`, `--issuer` or `--label` given for GitHub | drop it |
111
+
112
+ ## Rerun, rotate, remove
113
+
114
+ - Rerun: `pnpm everyapp signin add github` any time; values given replace,
115
+ settings not given are kept (Enter at a prompt keeps it).
116
+ - Rotate: **Generate a new client secret** on the OAuth App (an app holds
117
+ several at once), then `pnpm everyapp signin add github` with Enter at
118
+ the Client ID prompt, or the hidden read and
119
+ `pnpm everyapp signin add github --client-secret-from-env GITHUB_SIGNIN_CLIENT_SECRET --json`.
120
+ Then **Delete** the old secret on GitHub.
121
+ - Remove, only when asked: `pnpm everyapp signin remove github --json --yes`
122
+ (`--yes` only when they said so). Accounts made with it stay. Then delete
123
+ the OAuth App (its page → **Advanced** → **Delete application**).
124
+
125
+ ## Sources
126
+
127
+ - https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app
128
+ (Settings → Developer settings → OAuth apps → New OAuth App; fields;
129
+ organization owners; 100 apps per owner)
130
+ - https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps
131
+ (`read:user`, `user:email`)
132
+ - https://docs.github.com/en/apps/oauth-apps/maintaining-oauth-apps/troubleshooting-authorization-request-errors
133
+ and https://docs.github.com/en/apps/oauth-apps/maintaining-oauth-apps/troubleshooting-oauth-app-access-token-request-errors
134
+ (redirect URI mismatch, `incorrect_client_credentials`)
135
+ - The **Generate a new client secret**, **Advanced** and **Delete
136
+ application** labels are from GitHub's UI as the runbook records them;
137
+ GitHub's docs pages above don't show them (unverified).
138
+ - `docs/runbooks/sign-in-providers.md` ("GitHub") and
139
+ `packages/sdk/src/internal/signInProviders.ts`
@@ -0,0 +1,178 @@
1
+ # Sign in with Google on the Gateway
2
+
3
+ Read [README.md](README.md) first: the rules every setup follows.
4
+
5
+ Puts **Continue with Google** on the Gateway's sign-in page, beside the
6
+ password. At the end the person has an OAuth client in a Google Cloud
7
+ project, its two settings on the Gateway, and has signed in with Google
8
+ once. `pnpm everyapp signin show google --json` names this file as
9
+ `setup`, and the CLI prints `Guide: .claude/skills/everyapp/setup/signin-google.md`.
10
+
11
+ Who gets in doesn't change: Google only says who someone is (an email it
12
+ verified). A member signs in; someone with a pending invitation for that
13
+ exact address joins with the invited role, no link or password needed;
14
+ everyone else is refused ("You don't have an account on this gateway.")
15
+ and nothing is created. An existing password account connects Google once:
16
+ the first "Continue with Google" says an account exists, the person signs
17
+ in with the password and the page connects Google (or **Connect Google** in
18
+ the account menu). The full rules: `docs/runbooks/sign-in-providers.md`,
19
+ "Who gets in".
20
+
21
+ This is not the Google client apps use for Gmail and Calendar
22
+ (`GOOGLE_WORKSPACE_*`, [google.md](google.md)). Make a separate client
23
+ here. When that other client's project is *External* and still in Testing
24
+ (its Gmail scope waits on Google's verification), make this one in a
25
+ separate project too: publishing is per project, and this one must be
26
+ published (step 2).
27
+
28
+ ## Settings
29
+
30
+ | Name | Required | Flag | What it holds |
31
+ | ----------------------------- | -------- | -------------------------------- | --------------------------------------------------------------- |
32
+ | `GOOGLE_SIGNIN_CLIENT_ID` | required | `--client-id <id>` | the client's Client ID, ending in `.apps.googleusercontent.com`; not a secret |
33
+ | `GOOGLE_SIGNIN_CLIENT_SECRET` | required | `--client-secret-from-env <VAR>` | the client's Client secret, shown once at creation |
34
+
35
+ Scopes asked for: `openid email profile`, identity only. The email is the
36
+ ID token's `email`, and only when `email_verified` is true (every Google
37
+ account's own address is).
38
+
39
+ ## Costs and plans
40
+
41
+ Free: a Google Cloud project and an OAuth client for sign-in need no
42
+ billing account. The identity scopes are not sensitive, so the consent
43
+ screen can be published without Google's sensitive-scope verification
44
+ (whether Google still asks for brand verification before showing the
45
+ app's name and logo: unverified).
46
+
47
+ ## Only a person can
48
+
49
+ - Sign in to Google Cloud, create the project and the consent screen.
50
+ - Create the OAuth client and copy its secret off the screen.
51
+ - Publish the consent screen (In production).
52
+ - Type the secret at the hidden prompt of `pnpm everyapp signin add google`.
53
+ - Sign in with Google to check it.
54
+
55
+ ## Before you start
56
+
57
+ 1. `pnpm everyapp status --json`: `data.installed` and `data.claimed` true;
58
+ `data.gatewayUrl` gives `<gateway host>`. Not installed, or no owner:
59
+ [gateway.md](gateway.md) first (nobody signs in with a provider before
60
+ the Gateway is claimed). `data.access.on` true: see Verify 4.
61
+ 2. `pnpm everyapp signin show google --json` (changes nothing). `Unknown
62
+ command signin` (exit 251): the checkout predates sign-in providers;
63
+ `git pull` in the `every-app` folder, then again. Read `data.status`:
64
+ `off`, this file applies; `needs setup`, `data.missing` names what is
65
+ left (go to step 3); `on`, it works already: ask before replacing (a new
66
+ secret for the same client is harmless, see Rotate). `data.callbackUrl`
67
+ is the redirect URL, `https://<gateway host>/api/auth/callback/google`.
68
+ 3. Ask whether a Google Cloud project for this exists already, and whether
69
+ everyone who will sign in is in one Google Workspace organization
70
+ (then the consent screen can be *Internal*: no publishing needed).
71
+
72
+ ## Steps
73
+
74
+ 1. **You:** give the person `data.callbackUrl` from Before you start 2.
75
+ 2. **Person:** the consent screen, once per project. Open
76
+ https://console.cloud.google.com/auth/overview (pick or create the
77
+ project in the top bar) → **Get started**: App name (what people see,
78
+ for example `Every App`), User support email → Next; Audience
79
+ *Internal* (one Workspace organization) or *External* → Next; Contact
80
+ Information → Next; agree to the User Data Policy → **Create**.
81
+ *External* only: https://console.cloud.google.com/auth/audience →
82
+ **Publish app** → Confirm. While it says Testing, only the test users
83
+ listed there can sign in. Done when the Audience page says "In
84
+ production" (or the app is *Internal*).
85
+ 3. **Person:** the client. https://console.cloud.google.com/auth/clients →
86
+ **Create client** → Application type **Web application** → Name (for
87
+ example `Every App sign-in`) → **Authorized redirect URIs** → **Add
88
+ URI** → the exact URL from step 1 → **Create**. No JavaScript origins.
89
+ A dialog shows the Client ID and the Client secret; keep it open (the
90
+ secret is shown only now; afterwards only its last four characters).
91
+ Redirect URI changes can take 5 minutes to a few hours to apply.
92
+ 4. **Person:** set both on the Gateway, in a second terminal in the
93
+ `every-app` folder:
94
+ ```sh
95
+ pnpm everyapp signin add google
96
+ ```
97
+ It prints where the client is made and the redirect URL, then asks for
98
+ the Client ID (shown as typed) and the Client secret (hidden: pasting
99
+ works, nothing is echoed). It ends with Google on and the sign-in URL.
100
+ They tell you when it's done. Both go on in one new version of the
101
+ Gateway Worker, live on the next request.
102
+ Another way, when they'd rather not use the prompts: in that terminal
103
+ they read the secret in hidden (bash or zsh:
104
+ `read -rs GOOGLE_SIGNIN_CLIENT_SECRET && export GOOGLE_SIGNIN_CLIENT_SECRET`;
105
+ fish: `read -s -x GOOGLE_SIGNIN_CLIENT_SECRET`), paste, press Enter, then
106
+ run `pnpm everyapp signin add google --client-id <Client ID> --client-secret-from-env GOOGLE_SIGNIN_CLIENT_SECRET --json`
107
+ and `unset GOOGLE_SIGNIN_CLIENT_SECRET` (fish: `set -e GOOGLE_SIGNIN_CLIENT_SECRET`).
108
+ The Client ID isn't a secret, so it may sit on the command line.
109
+ 5. **Person:** close the client dialog (Google keeps no copy of the secret
110
+ to show again; a lost one is replaced in Rotate).
111
+
112
+ ## Verify
113
+
114
+ 1. `pnpm everyapp signin list --json`: the `google` entry has `status:
115
+ "on"` and `missing` empty.
116
+ 2. The person's address must be a member's or have a pending invitation.
117
+ For someone new: `pnpm everyapp workspace invite <email>` (it prints an
118
+ invitation link; for Google sign-in they don't need it, the pending
119
+ invitation for that exact address is enough).
120
+ 3. **Person:** in a private window open `https://<gateway host>/sign-in`:
121
+ **Continue with Google** is there. Select it, choose the Google account,
122
+ and they land on the Gateway signed in. An existing password account
123
+ first sees "An account with this email already exists": sign in with
124
+ the password once, and the page connects Google.
125
+ 4. Behind Cloudflare Access (`data.access.on`): nothing else changes, but
126
+ the Google account's address must be the one Access knows the person
127
+ by; otherwise the page says Access knows them by another email.
128
+ 5. Logs (README's Verify 4): `signin.config_problem` names a malformed or
129
+ missing setting, the reason the button stays off.
130
+
131
+ ## If it fails
132
+
133
+ | error or symptom | meaning | what to do |
134
+ | --- | --- | --- |
135
+ | no **Continue with Google** button | the provider isn't on: a setting missing or malformed, or the new version isn't live yet | `pnpm everyapp signin list --json` (`missing`); the logs' `signin.config_problem`; `signin add google` again |
136
+ | Google's "Error 400: redirect_uri_mismatch" | the client's redirect URI isn't exactly `data.callbackUrl` | fix it under Authorized redirect URIs (no trailing slash; `https`); wait a few minutes |
137
+ | Google refuses someone before the Gateway sees them (its wording for people not on Test users: unverified) | the consent screen is *External* and still in Testing: only test users get in, and their grants expire after seven days | **Publish app** (step 2) |
138
+ | Google's `org_internal` error | the consent screen is *Internal* and the person's Google account is outside that organization | they use their Workspace account, or the audience becomes *External* and published |
139
+ | "You don't have an account on this gateway." | the address Google returned isn't a member's and has no pending invitation | `pnpm everyapp workspace invite <email>` with the Google address, exactly as Google spells it |
140
+ | "The sign-in provider hasn't verified this email address." | Google reports the address unverified (a secondary, unverified address on a non-Gmail account) | the person verifies it at Google, or uses another account |
141
+ | "An account with this email already exists." | a password account has that address and isn't verified yet | sign in with the password once; the page connects Google |
142
+ | `SIGNIN_VALUE_REQUIRED` | unattended, and a required setting has no flag and isn't set yet | `data.missing` names it; rerun `data.rerun` with the flag |
143
+ | `SECRET_EMPTY` | the `--client-secret-from-env` variable is empty or unset in that shell | the hidden read again, in the same terminal |
144
+ | `SIGNIN_FLAG_NOT_APPLICABLE` | a flag Google doesn't take (`--tenant`, `--issuer`, `--label`) | drop it |
145
+ | Google's "invalid_client" | the secret doesn't belong to this Client ID (a typo, or another client's) | Rotate |
146
+
147
+ ## Rerun, rotate, remove
148
+
149
+ - Rerun: `pnpm everyapp signin add google` any time. A value given
150
+ replaces; a setting not given and already set is kept (at the prompt,
151
+ Enter keeps it). The done event's `data.set` and `data.kept` say which.
152
+ - Rotate: on the client's page (https://console.cloud.google.com/auth/clients
153
+ → the client) add a new secret, then the person runs
154
+ `pnpm everyapp signin add google` and presses Enter at the Client ID
155
+ prompt, pasting only the new secret; or the hidden read and
156
+ `pnpm everyapp signin add google --client-secret-from-env GOOGLE_SIGNIN_CLIENT_SECRET --json`
157
+ (the Client ID is kept). Then disable and delete the old secret there.
158
+ - Remove, only when the person asks:
159
+ `pnpm everyapp signin remove google --json --yes` (`--yes` only when they
160
+ said so; at a terminal it asks). Every `GOOGLE_SIGNIN_*` setting goes in
161
+ one version; people who signed in with Google keep their accounts and
162
+ use another way in. Then delete the client in the console.
163
+
164
+ ## Sources
165
+
166
+ - https://support.google.com/cloud/answer/15549257 (Clients page, Create
167
+ client, Web application, Authorized redirect URIs, the secret shown once,
168
+ 5 minutes to a few hours)
169
+ - https://developers.google.com/identity/openid-connect/openid-connect
170
+ (`openid email profile`, `email_verified`)
171
+ - https://support.google.com/cloud/answer/13464321 (which scopes need
172
+ verification; the identity scopes are not sensitive)
173
+ - https://support.google.com/cloud/answer/15549945 (Manage App Audience:
174
+ Internal, External, Testing and test users, seven-day grants, Publish
175
+ app, `org_internal`)
176
+ - `docs/runbooks/sign-in-providers.md` ("Who gets in", "Existing accounts",
177
+ "Google", "Troubleshooting") and the catalog,
178
+ `packages/sdk/src/internal/signInProviders.ts`
@@ -0,0 +1,183 @@
1
+ # Sign in with Microsoft on the Gateway
2
+
3
+ Read [README.md](README.md) first: the rules every setup follows.
4
+
5
+ Puts **Continue with Microsoft** on the Gateway's sign-in page, beside the
6
+ password: work or school accounts from the person's Microsoft Entra
7
+ directory (Microsoft 365), or any Microsoft account. At the end the person
8
+ has an app registration in Entra, its settings on the Gateway, and has
9
+ signed in with Microsoft once. `pnpm everyapp signin show microsoft --json`
10
+ names this file as `setup`, and the CLI prints
11
+ `Guide: .claude/skills/everyapp/setup/signin-microsoft.md`.
12
+
13
+ Who gets in doesn't change: Microsoft only says who someone is. A member
14
+ signs in; someone with a pending invitation for that exact address joins
15
+ with the invited role; everyone else is refused ("You don't have an
16
+ account on this gateway.") and nothing is created. An existing password
17
+ account connects Microsoft once, by signing in with the password when the
18
+ page asks. The rules: `docs/runbooks/sign-in-providers.md`, "Who gets in".
19
+
20
+ Not the Outlook mail and calendar client apps use (`MICROSOFT_365_*`,
21
+ [microsoft-365.md](microsoft-365.md)): make a separate registration here.
22
+
23
+ **Which tenant decides which addresses count as verified.** Recommended:
24
+ the registration is single tenant and `MICROSOFT_SIGNIN_TENANT` is the
25
+ Directory (tenant) ID, so only the person's own directory signs in, and
26
+ its addresses are trusted. Otherwise (unset, `common`, `organizations`,
27
+ `consumers`) any tenant's admin can put any address on their users
28
+ (Microsoft calls the attack "nOAuth"), so the Gateway trusts an address
29
+ only with the token's own word for it: the optional claims `email` and
30
+ `xms_edov` (step 4). Without them every sign-in is refused as unverified.
31
+
32
+ ## Settings
33
+
34
+ | Name | Required | Flag | What it holds |
35
+ | -------------------------------- | -------- | -------------------------------- | ---------------------------------------------------------------- |
36
+ | `MICROSOFT_SIGNIN_CLIENT_ID` | required | `--client-id <id>` | the registration's Application (client) ID; not a secret |
37
+ | `MICROSOFT_SIGNIN_CLIENT_SECRET` | required | `--client-secret-from-env <VAR>` | a client secret's **Value** (it expires; 24 months at most) |
38
+ | `MICROSOFT_SIGNIN_TENANT` | optional | `--tenant <GUID\|common\|organizations\|consumers>` | the Directory (tenant) ID; unset means any Microsoft account (`common`) |
39
+
40
+ Scopes asked for: `openid profile email`; no Graph scope, no refresh token
41
+ kept. The email is the ID token's `email`, verified when the tenant is
42
+ pinned, or the token carries `xms_edov: true`, `email_verified: true`, a
43
+ matching `verified_primary_email`, or comes from a personal account.
44
+
45
+ ## Costs and plans
46
+
47
+ No charge for an app registration: any Microsoft Entra tenant has them,
48
+ Entra ID Free included (a Microsoft 365 business subscription comes with a
49
+ tenant). Microsoft's quickstart lists an Azure account as a prerequisite;
50
+ whether a tenant with no Azure subscription can register apps: unverified.
51
+ The client secret expires (24 months at most; Microsoft recommends under
52
+ 12), so a reminder is part of the setup.
53
+
54
+ ## Only a person can
55
+
56
+ - Sign in to the Microsoft Entra admin center (at least the Application
57
+ Developer role to register; adding optional claims is documented for
58
+ Cloud Application Administrator, or the app's owner).
59
+ - Create the registration and the client secret, and copy its Value.
60
+ - Type the secret at the hidden prompt of `pnpm everyapp signin add microsoft`.
61
+ - Choose single tenant or any account; sign in with Microsoft to check.
62
+
63
+ ## Before you start
64
+
65
+ 1. `pnpm everyapp status --json`: `data.installed` and `data.claimed` true;
66
+ `data.gatewayUrl` gives `<gateway host>`. Not installed or no owner:
67
+ [gateway.md](gateway.md) first. `data.access.on` true: see Verify 4.
68
+ 2. `pnpm everyapp signin show microsoft --json` (changes nothing; `Unknown
69
+ command signin`: `git pull` in the `every-app` folder). `data.status`
70
+ `off`: this file applies; `needs setup`: `data.missing` names what is
71
+ left (step 5); `on`: ask before replacing. `data.callbackUrl` is the
72
+ redirect URL, `https://<gateway host>/api/auth/callback/microsoft`;
73
+ `data.set` says whether `MICROSOFT_SIGNIN_TENANT` is set.
74
+ 3. Ask: only people in your own Microsoft 365 directory (recommended:
75
+ single tenant), or any Microsoft account (guests from other companies,
76
+ outlook.com addresses)? It decides steps 2 and 4.
77
+
78
+ ## Steps
79
+
80
+ 1. **You:** give the person `data.callbackUrl`.
81
+ 2. **Person:** https://entra.microsoft.com → **Entra ID** → **App
82
+ registrations** → **New registration**. Name (what people see, for
83
+ example `Every App`). **Supported account types**:
84
+ **Single tenant only - <your tenant>** (recommended), or **Any Entra ID
85
+ Tenant + Personal Microsoft accounts** for anyone. **Register**. The **Overview** page
86
+ shows **Application (client) ID** and **Directory (tenant) ID**: both
87
+ are plain ids, not secrets.
88
+ 3. **Person:** the redirect URL. Under **Manage** → **Authentication** →
89
+ **Redirect URI configuration** → **Add Redirect URI** → **Web** → the
90
+ exact URL from step 1 → **Configure**.
91
+ 4. **Person,** only when not single tenant: **Token configuration** → **Add
92
+ optional claim** → token type **ID** → tick `email` and `xms_edov` →
93
+ **Add** (accept the prompt to turn on the Microsoft Graph email
94
+ permission if it appears). If `xms_edov` isn't in the list (reported
95
+ for some apps while the claim was in preview; unverified today):
96
+ **Manifest** → under `optionalClaims` → `idToken` add
97
+ `{ "name": "xms_edov", "essential": false }` beside `email` → **Save**.
98
+ 5. **Person:** the secret. **Certificates & secrets** → **Client secrets**
99
+ → **New client secret** → a description, an expiry (the longest is 24
100
+ months) → **Add**. Copy the **Value** column now (not the Secret ID): it
101
+ is never shown again after leaving the page. Set a calendar reminder
102
+ for a week before it expires.
103
+ 6. **Person:** in a second terminal in the `every-app` folder:
104
+ ```sh
105
+ pnpm everyapp signin add microsoft
106
+ ```
107
+ It asks for the Application (client) ID (shown as typed), the client
108
+ secret Value (hidden: pasting works, nothing is echoed) and the tenant
109
+ (shown as typed: the Directory (tenant) ID for single tenant; Enter
110
+ leaves it unset). One new Gateway Worker version, live on the next
111
+ request. They tell you when it's done.
112
+ Another way: in that terminal, the hidden read (bash or zsh:
113
+ `read -rs MICROSOFT_SIGNIN_CLIENT_SECRET && export MICROSOFT_SIGNIN_CLIENT_SECRET`;
114
+ fish: `read -s -x MICROSOFT_SIGNIN_CLIENT_SECRET`), paste, Enter, then
115
+ `pnpm everyapp signin add microsoft --client-id <Application (client) ID> --tenant <Directory (tenant) ID> --client-secret-from-env MICROSOFT_SIGNIN_CLIENT_SECRET --json`
116
+ (leave `--tenant` off for any account), then `unset MICROSOFT_SIGNIN_CLIENT_SECRET`
117
+ (fish: `set -e MICROSOFT_SIGNIN_CLIENT_SECRET`).
118
+
119
+ ## Verify
120
+
121
+ 1. `pnpm everyapp signin list --json`: the `microsoft` entry has `status:
122
+ "on"` and `missing` empty; for single tenant, `pnpm everyapp signin show microsoft --json`
123
+ lists `MICROSOFT_SIGNIN_TENANT` in `data.set`.
124
+ 2. The person's address must be a member's or have a pending invitation:
125
+ `pnpm everyapp workspace invite <email>` for someone new (the pending
126
+ invitation for that exact address is enough; the printed link isn't
127
+ needed for this).
128
+ 3. **Person:** private window, `https://<gateway host>/sign-in` →
129
+ **Continue with Microsoft** → their work account → (first time) the
130
+ consent screen → **Accept** → the Gateway, signed in.
131
+ 4. Behind Cloudflare Access: nothing else changes, but the Microsoft
132
+ address must be the one Access knows the person by.
133
+ 5. Logs (README's Verify 4): `signin.config_problem` names a malformed
134
+ setting (a tenant that isn't a GUID or one of the three words).
135
+
136
+ ## If it fails
137
+
138
+ | error or symptom | meaning | what to do |
139
+ | --- | --- | --- |
140
+ | no **Continue with Microsoft** button | a setting missing or malformed, or the version isn't live yet | `pnpm everyapp signin list --json`; logs' `signin.config_problem` |
141
+ | `AADSTS50011` (redirect URI mismatch) | the Web redirect URI isn't exactly `data.callbackUrl` | fix it under Authentication (Web, not Single-page application) |
142
+ | "The sign-in provider hasn't verified this email address." | not single tenant, and the token lacks `xms_edov`/`email` | step 4, or pin the tenant (single-tenant registration plus `--tenant`) |
143
+ | `AADSTS50020` or "user account … does not exist in tenant" | single tenant, and the person's account is from another directory | invite them as a guest in Entra, or use any-account mode with step 4 |
144
+ | `AADSTS7000215` (invalid client secret) | the Secret ID was copied instead of the Value, or the secret expired | Rotate |
145
+ | "You don't have an account on this gateway." | no member or invitation for that address | `pnpm everyapp workspace invite <email>` with the address Microsoft returns |
146
+ | `SIGNIN_TENANT_INVALID` | `--tenant` isn't a GUID, `common`, `organizations` or `consumers` | the Directory (tenant) ID from Overview |
147
+ | `SIGNIN_VALUE_REQUIRED` / `SECRET_EMPTY` | unattended with a required setting neither given nor set / the env variable empty in that shell | `data.rerun` with the flag / the hidden read again in the same terminal |
148
+ | sign-in stops after months | the client secret expired | Rotate |
149
+
150
+ ## Rerun, rotate, remove
151
+
152
+ - Rerun: `pnpm everyapp signin add microsoft` any time; values given
153
+ replace, settings not given are kept (Enter at a prompt keeps it).
154
+ - Rotate, before the expiry: **Certificates & secrets** → **New client
155
+ secret**, then `pnpm everyapp signin add microsoft` (Enter at the client
156
+ id and tenant prompts, paste the new Value), or the hidden read and
157
+ `pnpm everyapp signin add microsoft --client-secret-from-env MICROSOFT_SIGNIN_CLIENT_SECRET --json`.
158
+ Then delete the old secret in Entra.
159
+ - Drop the tenant pin (any account, after step 4):
160
+ `pnpm everyapp secret unset MICROSOFT_SIGNIN_TENANT --json` (asks at a
161
+ terminal; with `--json`, `--yes` only when the person said so).
162
+ - Remove, only when asked: `pnpm everyapp signin remove microsoft --json --yes`
163
+ (`--yes` only when they said so). Accounts made with it stay. Then delete
164
+ the registration in Entra.
165
+
166
+ ## Sources
167
+
168
+ - https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app
169
+ (Entra ID → App registrations → New registration; Supported account
170
+ types; Application (client) ID; Application Developer role)
171
+ - https://learn.microsoft.com/en-us/entra/identity-platform/how-to-add-redirect-uri
172
+ (Authentication → Redirect URI configuration → Add Redirect URI → Web)
173
+ - https://learn.microsoft.com/en-us/entra/identity-platform/how-to-add-credentials
174
+ (New client secret; 24 months at most, under 12 recommended; the Value
175
+ is never shown again)
176
+ - https://learn.microsoft.com/en-us/entra/identity-platform/optional-claims
177
+ (Token configuration → Add optional claim; the manifest's `optionalClaims`)
178
+ - https://learn.microsoft.com/en-us/entra/identity-platform/optional-claims-reference
179
+ (`xms_edov`: needs the `email` claim; `verified_primary_email`)
180
+ - https://learn.microsoft.com/en-us/entra/identity-platform/migrate-off-email-claim-authorization
181
+ (nOAuth: never trust `email` alone)
182
+ - `docs/runbooks/sign-in-providers.md` ("Microsoft", "Troubleshooting") and
183
+ `packages/sdk/src/internal/signInProviders.ts`