tenseleyflow/shithub / 2974738

Browse files

docs/api: device-code grant reference

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
2974738978da621c228147a649d6ec113f3000c7
Parents
6bde41f
Tree
54e5f41

1 changed file

StatusFile+-
M docs/public/api/auth.md 118 11
docs/public/api/auth.mdmodified
@@ -1,7 +1,8 @@
11
 # Authentication
22
 
3
-shithub's API is PAT-only. There is no OAuth / device-flow / JWT
4
-issuance endpoint today.
3
+shithub's API authenticates calls with Personal Access Tokens.
4
+PATs are minted via the web UI or, for CLI / non-browser clients,
5
+through the [device-code grant](#device-code-grant-rfc-8628).
56
 
67
 ## Header
78
 
@@ -42,14 +43,120 @@ and means every API caller is identified by an auditable token.
4243
 
4344
 ## Creating a token programmatically
4445
 
45
-There is no API for creating PATs; tokens are only created from
46
-the web UI. This is intentional — the create-PAT surface is the
47
-account's most security-sensitive non-password operation.
46
+Two paths exist:
4847
 
49
-## Future
48
+1. **Web UI** — sign in at `/login`, then mint a token at
49
+   `/settings/tokens`. Returns the raw token exactly once.
50
+2. **Device-code grant** — the standardised CLI / TV / IoT flow,
51
+   documented in the next section. The client_id must be in the
52
+   server's allowlist (default: `shithub-cli`).
5053
 
51
-OAuth-style application authorizations (`client_id` + `client_
52
-secret`, `code` exchange, refresh tokens) are planned post-MVP.
53
-For now, instruct human users to mint a PAT from their settings
54
-and supply it to your app via the operator's secret-management
55
-flow.
54
+## Device-code grant (RFC 8628)
55
+
56
+The device-code grant lets a CLI obtain a PAT without prompting
57
+the user to paste a token. It mirrors GitHub's `/login/device/*`
58
+shape verbatim.
59
+
60
+### Endpoints
61
+
62
+```
63
+POST /login/device/code                request a new authorization
64
+POST /login/oauth/access_token         poll until the user approves
65
+GET  /login/device                     browser verification page
66
+```
67
+
68
+`/login/device/code` and `/login/oauth/access_token` are CSRF-exempt
69
+and accept `application/x-www-form-urlencoded` bodies.
70
+
71
+### 1. Request a device code
72
+
73
+```
74
+POST /login/device/code
75
+Content-Type: application/x-www-form-urlencoded
76
+
77
+client_id=shithub-cli&scope=user%3Aread,repo%3Aread
78
+```
79
+
80
+```json
81
+{
82
+  "device_code":               "f0a1b2c3...",
83
+  "user_code":                 "ABCD-EFGH",
84
+  "verification_uri":          "https://shithub.example/login/device",
85
+  "verification_uri_complete": "https://shithub.example/login/device?user_code=ABCD-EFGH",
86
+  "expires_in": 900,
87
+  "interval":   5
88
+}
89
+```
90
+
91
+- `client_id` must be in the server's allowlist. Default: `shithub-cli`.
92
+- `scope` is space- or comma-separated. Omit to receive
93
+  `user:read`. Unknown scopes return `invalid_scope`.
94
+- `device_code` is returned once and never echoed back; store it
95
+  in client memory only.
96
+- `interval` is the minimum seconds between polls — see
97
+  `slow_down` below.
98
+
99
+### 2. Show the user the code
100
+
101
+Open the user's browser to `verification_uri_complete` (or
102
+`verification_uri` if you can't open URLs with query strings).
103
+The user enters the `user_code`, signs in if needed, then clicks
104
+**Authorize** or **Deny**.
105
+
106
+### 3. Poll the exchange endpoint
107
+
108
+```
109
+POST /login/oauth/access_token
110
+Content-Type: application/x-www-form-urlencoded
111
+
112
+grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code
113
+&client_id=shithub-cli
114
+&device_code=f0a1b2c3...
115
+```
116
+
117
+Successful exchange (after the user has approved):
118
+
119
+```json
120
+{
121
+  "access_token": "shithub_pat_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
122
+  "token_type":   "bearer",
123
+  "scope":        "user:read,repo:read"
124
+}
125
+```
126
+
127
+The access token is disclosed **exactly once**. A second exchange
128
+of the same `device_code` returns `invalid_grant` even after
129
+successful approval — clients must cache the token from the
130
+first 200 response.
131
+
132
+### Error codes
133
+
134
+All errors are HTTP 400 with a JSON body:
135
+
136
+```json
137
+{ "error": "<code>", "error_description": "..." }
138
+```
139
+
140
+| `error`                | Meaning                                                                       |
141
+|------------------------|-------------------------------------------------------------------------------|
142
+| `authorization_pending`| User has not approved or denied yet. Keep polling at `interval` seconds.      |
143
+| `slow_down`            | You polled inside the `interval` window. Increase your delay and retry.       |
144
+| `access_denied`        | The user explicitly denied the request. Stop polling.                         |
145
+| `expired_token`        | The grant outlived its `expires_in`. Restart from `/login/device/code`.       |
146
+| `invalid_grant`        | `device_code` unknown or already exchanged.                                   |
147
+| `unauthorized_client`  | `client_id` is not in the server's allowlist.                                 |
148
+| `invalid_scope`        | One or more requested scopes is not a known shithub scope.                    |
149
+| `unsupported_grant_type`| The exchange request used a non-device-code grant_type.                       |
150
+| `invalid_request`      | Required form fields missing or body malformed.                               |
151
+
152
+### Lifecycle invariants
153
+
154
+- The device_code is single-use for token disclosure. After a
155
+  successful exchange the row stays in the database for forensics
156
+  but further exchanges always return `invalid_grant`.
157
+- The user_code is human-typeable: 8 characters from a 32-symbol
158
+  alphabet that excludes 0/O/1/I, formatted `XXXX-XXXX`. The
159
+  verification page also accepts the unhyphenated form.
160
+- Issued PATs carry the scopes requested at `/login/device/code`
161
+  time. The token is named on the user's `/settings/tokens` page
162
+  with a recognisable label derived from the CLI's `User-Agent`.