-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathoauth.graphql
More file actions
113 lines (106 loc) · 5.16 KB
/
Copy pathoauth.graphql
File metadata and controls
113 lines (106 loc) · 5.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
## OAuth Plugin Schema
## This defines the tables needed for OAuth functionality
## CSRF Token storage table in oauth database
## Stores temporary CSRF tokens for OAuth flows (10 minute expiration)
type csrf_tokens @table(database: "oauth", expiration: 600) {
token_id: ID @primaryKey
data: String # JSON stringified CSRFTokenData
created_at: Float @createdTime # Harper-assigned on insert (epoch ms)
}
## MCP Dynamic Client Registration table (RFC 7591)
## Stores OAuth client registrations for MCP clients (Claude Desktop, Cursor,
## mcp-remote, etc.). No expiration: clients like Claude Desktop cache their
## issued client_id across launches, so registrations must survive restarts.
## Array-valued fields (redirect_uris, contacts, grant_types, response_types)
## are stored as JSON-encoded strings to match the existing csrf_tokens pattern.
type harper_oauth_mcp_clients @table(database: "oauth") {
client_id: ID @primaryKey
client_secret: String
redirect_uris: String # JSON array of allowed redirect URIs
client_name: String
client_uri: String
logo_uri: String
scope: String
contacts: String # JSON array
grant_types: String # JSON array; default ["authorization_code", "refresh_token"]
response_types: String # JSON array; default ["code"]
token_endpoint_auth_method: String # "none" for public clients (default), "client_secret_basic", "client_secret_post"
application_type: String # "web" (default) or "native"
software_id: String
software_version: String
client_id_issued_at: Float
client_secret_expires_at: Float # 0 = never expires
}
## MCP Authorization Codes (OAuth 2.1 + RFC 7636 PKCE + RFC 8707 audience)
## Short-lived codes minted by /oauth/mcp/authorize on successful upstream auth.
## Single-use: /oauth/mcp/token (Stage 4) reads-then-deletes. TTL via Harper.
## Auto-expires after 5 minutes — codes are exchanged immediately after the
## upstream redirect, so a longer window only widens the replay attack surface.
type mcp_auth_codes @table(database: "oauth", expiration: 300) {
code: ID @primaryKey
client_id: String
user: String # Harper user identifier
resource: String # RFC 8707 audience the issued token will be bound to
code_challenge: String # RFC 7636 PKCE; verified at /token
code_challenge_method: String # Always "S256"
redirect_uri: String # Bound at issuance; /token must match
scope: String # Space-separated; may be empty
created_at: Float @createdTime # Harper-assigned on insert (epoch ms)
}
## MCP JWT signing keys (Stage 4)
## Persisted here (not on disk) so all replicated Harper nodes share one key
## set — file storage would diverge across nodes. No expiration: keys are
## retained for verification until explicitly rotated. Per-row `alg` (RS256 or
## ES256; EdDSA unsupported — jsonwebtoken cannot emit it); the public half is published at
## /.well-known/jwks.json, the private half never leaves the server.
type harper_oauth_mcp_keys @table(database: "oauth") {
kid: ID @primaryKey
alg: String # "RS256" | "ES256"
public_key_pem: String
private_key_pem: String
# Hand-managed (NOT @createdTime): keyStore deliberately mutates created_at
# to reorder GC/signer selection, and @createdTime would pin it to the
# original insert time. Unit is Unix SECONDS (keyStore computes nowSeconds -
# created_at) — do not conflate with the epoch-ms @createdTime fields above.
created_at: Float
}
## MCP refresh-token families (Stage 4, OAuth 2.1 single-use rotation)
## One row per refresh-token family. The token handed to the client is
## "<family_id>.<secret>"; only the SHA-256 hash of the whole value is stored
## (current_token_hash). A presented token whose hash != current_token_hash is
## a replay of an already-rotated token → the family is revoked. Real TTL is
## enforced at runtime via expires_at; the table expiration is a generous GC
## backstop (30d) so abandoned families are eventually evicted.
type mcp_refresh_families @table(database: "oauth", expiration: 2592000) {
family_id: ID @primaryKey
current_token_hash: String
revoked: Boolean
client_id: String
user: String
resource: String
scope: String
created_at: Float @createdTime # Harper-assigned on insert; retained across rotation writes (epoch ms)
expires_at: Float # Unix seconds; refresh rejected once exceeded
}
## MCP client-assertion replay guard (RFC 7523 jti; #159/#160)
## One row per seen (client_id, jti); id = sha256(len:client_id:jti), length-
## prefixed so crafted inputs can't collide. Rows only need to outlive the
## maximum assertion window (60s exp + clock tolerance), so a 120s TTL evicts
## them. Enforcement is Table.create() insert-if-absent; see the
## assertionJtiStore.ts header for the residual-race notes (existence check
## is pre-staging snapshot only — harper#1745).
type mcp_assertion_jtis @table(database: "oauth", expiration: 120) {
id: ID @primaryKey
client_id: String
created_at: Float @createdTime # Harper-assigned (epoch ms); not hand-written
}
## OAuth User Session table (optional, for future use)
## Could store OAuth-specific user data
# type oauth_sessions @table(database: "oauth") {
# username: ID @primaryKey
# provider: String @indexed
# provider_id: String
# access_token: String
# refresh_token: String
# expires_at: Float
# }