Repository navigation
Expand file tree
/
Copy pathAuthOAuthServer.swift
More file actions
128 lines (119 loc) · 5.55 KB
/
Copy pathAuthOAuthServer.swift
File metadata and controls
128 lines (119 loc) · 5.55 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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
//
// AuthOAuthServer.swift
//
//
// Created by Guilherme Souza on 10/07/26.
//
public import Foundation
/// The OAuth 2.1 authorization-server consent and grant-management API.
///
/// Lets the signed-in user approve or deny a pending OAuth authorization
/// request from a third-party app registered against this project, and view
/// or revoke grants they've already given.
///
/// Access this namespace via ``AuthClient/oauthServer``.
///
/// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth. Not
/// to be confused with ``AuthAdminClient/oauth``, which manages OAuth *client*
/// registrations (an admin-only, secret-key operation).
///
/// ## Topics
///
/// ### Handling a pending authorization
/// - ``authorizationDetails(id:)``
/// - ``approveAuthorization(id:)``
/// - ``denyAuthorization(id:)``
///
/// ### Managing grants
/// - ``listGrants()``
/// - ``revokeGrant(clientID:)``
public struct AuthOAuthServer: Sendable {
/// Held strongly: the client vends this value and never stores it, so there is no cycle, and a
/// value kept past its call site keeps the client alive instead of dangling.
let client: AuthClient
var api: SessionAPIClient { client.dependencies.sessionAPI }
var authAPI: AuthAPI { client.dependencies.authAPI }
/// Fetches details about a pending OAuth authorization request, to present
/// a consent screen to the user.
///
/// If the user already has an active consent covering the requested
/// scopes for this client, the server auto-approves the request and this
/// returns ``OAuthAuthorizationDetailsResult/redirect(_:)`` instead of
/// ``OAuthAuthorizationDetailsResult/details(_:)`` — callers must handle
/// both cases.
///
/// - Important: Call this before ``approveAuthorization(id:)``
/// or ``denyAuthorization(id:)``. The authorization request
/// is created without an owning user (to support flows where the user
/// hasn't signed in yet), and this call is what claims it for the
/// current user server-side. Calling approve/deny first fails as if the
/// authorization didn't exist.
///
/// - Parameter id: The opaque identifier of the authorization request.
/// - Returns: Either the details to present for consent, or a redirect if already approved.
/// - Throws: ``AuthError/sessionMissing`` when no user is signed in. Else the error of the
/// refresh or of the request. `CancellationError` when the task is cancelled.
public func authorizationDetails(
id: String
) async throws -> OAuthAuthorizationDetailsResult {
try await api.authorizedExecute { authAPI.oauthAuthorizationDetails(id: id, accessToken: $0) }
}
/// Approves a pending OAuth authorization request.
///
/// - Important: ``authorizationDetails(id:)`` must be
/// called for this `id` first — it claims the request for
/// the current user, without which this fails as if the authorization
/// didn't exist.
///
/// - Parameter id: The opaque identifier of the authorization request.
/// - Returns: The URL to redirect the user to, completing the third-party app's OAuth flow.
/// - Throws: ``AuthError/sessionMissing`` when no user is signed in. Else the error of the
/// refresh or of the request. `CancellationError` when the task is cancelled.
public func approveAuthorization(id: String) async throws -> OAuthRedirect {
try await consent(authorizationID: id, action: "approve")
}
/// Denies a pending OAuth authorization request.
///
/// This does not throw on a normal denial: the server returns a redirect
/// URL whose query string carries an `error=access_denied` parameter
/// (RFC 6749), which the caller should navigate the user to so the
/// third-party app receives the OAuth error.
///
/// - Important: ``authorizationDetails(id:)`` must be
/// called for this `id` first — it claims the request for
/// the current user, without which this fails as if the authorization
/// didn't exist.
///
/// - Parameter id: The opaque identifier of the authorization request.
/// - Returns: The URL to redirect the user to, carrying the OAuth error.
/// - Throws: ``AuthError/sessionMissing`` when no user is signed in. Else the error of the
/// refresh or of the request. `CancellationError` when the task is cancelled.
public func denyAuthorization(id: String) async throws -> OAuthRedirect {
try await consent(authorizationID: id, action: "deny")
}
private func consent(authorizationID: String, action: String) async throws -> OAuthRedirect {
try await api.authorizedExecute {
try authAPI.oauthConsent(authorizationID: authorizationID, action: action, accessToken: $0)
}
}
/// Lists the OAuth grants the user has given to third-party client apps.
///
/// - Returns: The active grants.
/// - Throws: ``AuthError`` when the request fails or no session is stored.
public func listGrants() async throws -> [OAuthGrant] {
try await api.authorizedExecute { authAPI.oauthGrants(accessToken: $0) }
}
/// Revokes a previously granted OAuth consent.
///
/// This marks the consent as revoked, deletes active sessions for that
/// OAuth client, and invalidates its associated refresh tokens.
///
/// - Parameter clientID: The ID of the OAuth client to revoke access for.
/// - Throws: ``AuthError/sessionMissing`` when no user is signed in. Else the error of the
/// refresh or of the request. `CancellationError` when the task is cancelled.
public func revokeGrant(clientID: UUID) async throws {
try await api.authorizedExecute {
authAPI.revokeOAuthGrant(clientID: clientID, accessToken: $0)
}
}
}