feat(auth): implement response_type=id authentication flow

Implements both IndieAuth flows per W3C specification:
- Authentication flow (response_type=id): Code redeemed at authorization endpoint, returns only user identity
- Authorization flow (response_type=code): Code redeemed at token endpoint, returns access token

Changes:
- Authorization endpoint GET: Accept response_type=id (default) and code
- Authorization endpoint POST: Handle code verification for authentication flow
- Token endpoint: Validate response_type=code for authorization flow
- Store response_type in authorization code metadata
- Update metadata endpoint: response_types_supported=[code, id], code_challenge_methods_supported=[S256]

The default behavior now correctly defaults to response_type=id when omitted, per IndieAuth spec section 5.2.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-22 12:23:20 -07:00
parent 9dfa77633a
commit 052d3ad3e1
11 changed files with 684 additions and 28 deletions

View File

@@ -212,7 +212,8 @@ class DomainVerificationService:
code_challenge: str,
code_challenge_method: str,
scope: str,
me: str
me: str,
response_type: str = "id"
) -> str:
"""
Create authorization code with metadata.
@@ -225,6 +226,7 @@ class DomainVerificationService:
code_challenge_method: PKCE method (S256)
scope: Requested scope
me: Verified user identity
response_type: "id" for authentication, "code" for authorization
Returns:
Authorization code
@@ -232,7 +234,7 @@ class DomainVerificationService:
# Generate authorization code
authorization_code = self._generate_authorization_code()
# Create metadata
# Create metadata including response_type for flow determination during redemption
metadata = {
"client_id": client_id,
"redirect_uri": redirect_uri,
@@ -241,6 +243,7 @@ class DomainVerificationService:
"code_challenge_method": code_challenge_method,
"scope": scope,
"me": me,
"response_type": response_type,
"created_at": int(time.time()),
"expires_at": int(time.time()) + 600,
"used": False