// Shared client-issuing primitives used by both RFC 7591 registration // (register.go) and the app-owned fonoteka:oauth-client operator command // (08-CONTEXT.md D-19; T-08-SECRET-TIMING: "Shared hash/validation path and // one-time secret"). Exporting these rather than letting the command // re-derive its own random-id/hash/redirect-URI-validation logic keeps // exactly one code path responsible for how an OAuth client secret is // generated and hashed. package wristband // IssueClientCredentials mints a random opaque client_id (16 raw bytes, // base64url) and, for every token_endpoint_auth_method other than "none", a // client_secret (32 raw bytes) plus its sha256 hex hash -- the identical // fixed transform RFC 7591 registration uses (D-04). secret is "" and // secretHash is nil for a public ("none") client. The raw secret is // returned exactly once; only secretHash is meant to be persisted. func IssueClientCredentials(authMethod string) (clientID, secret string, secretHash *string, err error) { clientID, err = randomBase64URL(16) if err != nil { return "", "", nil, err } if authMethod == "none" { return clientID, "", nil, nil } secret, err = randomBase64URL(32) if err != nil { return "", "", nil, err } h := sha256Hex(secret) return clientID, secret, &h, nil } // RejectRedirectURI is the exported form of the redirect-URI validation // RFC 7591 registration already enforces (PHP OAuthClient::rejectRedirectUri): // at most 512 characters, a valid URL with scheme+host, https:// or loopback // http://127.0.0.1 / http://localhost. It returns "" when uri is accepted, // or a human-readable rejection reason otherwise. The operator command // shares this exact rule with DCR rather than re-deriving it (D-19). func RejectRedirectURI(uri string) string { return rejectRedirectURI(uri) }