Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Sources/Auth/AuthAdmin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ public struct AuthAdmin: Sendable {
var api: APIClient { Dependencies[clientID].api }
var encoder: JSONEncoder { Dependencies[clientID].encoder }

/// Contains all OAuth client administration methods.
/// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
///
/// - Warning: This property requires `service_role` key. Be careful to never expose your `service_role` key in the browser.
public var oauth: AuthAdminOAuth {
AuthAdminOAuth(clientID: clientID)
}

/// Get user by id.
/// - Parameter uid: The user's unique identifier.
/// - Note: This function should only be called on a server. Never expose your `service_role` key in the browser.
Expand Down
134 changes: 134 additions & 0 deletions Sources/Auth/AuthAdminOAuth.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//
// AuthAdminOAuth.swift
//
//
// Created by Guilherme Souza on 02/10/25.
//

import Foundation
import HTTPTypes

/// Contains all OAuth client administration methods.
/// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
public struct AuthAdminOAuth: Sendable {
let clientID: AuthClientID

var configuration: AuthClient.Configuration { Dependencies[clientID].configuration }
var api: APIClient { Dependencies[clientID].api }
var encoder: JSONEncoder { Dependencies[clientID].encoder }

/// Lists all OAuth clients with optional pagination.
/// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
///
/// - Note: This function should only be called on a server. Never expose your `service_role` key in the browser.
public func listClients(
params: PageParams? = nil
) async throws -> ListOAuthClientsPaginatedResponse {
struct Response: Decodable {
let clients: [OAuthClient]
let aud: String
}

let httpResponse = try await api.execute(
HTTPRequest(
url: configuration.url.appendingPathComponent("admin/oauth/clients"),
method: .get,
query: [
URLQueryItem(name: "page", value: params?.page?.description ?? ""),
URLQueryItem(name: "per_page", value: params?.perPage?.description ?? ""),
]
)
)

let response = try httpResponse.decoded(as: Response.self, decoder: configuration.decoder)

var pagination = ListOAuthClientsPaginatedResponse(
clients: response.clients,
aud: response.aud,
lastPage: 0,
total: httpResponse.headers[.xTotalCount].flatMap(Int.init) ?? 0
)

let links = httpResponse.headers[.link]?.components(separatedBy: ",") ?? []
if !links.isEmpty {
for link in links {
let page = link.components(separatedBy: ";")[0].components(separatedBy: "=")[1].prefix(
while: \.isNumber
)
let rel = link.components(separatedBy: ";")[1].components(separatedBy: "=")[1]

if rel == "\"last\"", let lastPage = Int(page) {
pagination.lastPage = lastPage
} else if rel == "\"next\"", let nextPage = Int(page) {
pagination.nextPage = nextPage
}
}
}

return pagination
}

/// Creates a new OAuth client.
/// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
///
/// - Note: This function should only be called on a server. Never expose your `service_role` key in the browser.
@discardableResult
public func createClient(params: CreateOAuthClientParams) async throws -> OAuthClient {
try await api.execute(
HTTPRequest(
url: configuration.url.appendingPathComponent("admin/oauth/clients"),
method: .post,
body: encoder.encode(params)
)
)
.decoded(decoder: configuration.decoder)
}

/// Gets details of a specific OAuth client.
/// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
///
/// - Parameter clientId: The unique identifier of the OAuth client.
/// - Note: This function should only be called on a server. Never expose your `service_role` key in the browser.
public func getClient(clientId: String) async throws -> OAuthClient {
try await api.execute(
HTTPRequest(
url: configuration.url.appendingPathComponent("admin/oauth/clients/\(clientId)"),
method: .get
)
)
.decoded(decoder: configuration.decoder)
}

/// Deletes an OAuth client.
/// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
///
/// - Parameter clientId: The unique identifier of the OAuth client to delete.
/// - Note: This function should only be called on a server. Never expose your `service_role` key in the browser.
@discardableResult
public func deleteClient(clientId: String) async throws -> OAuthClient {
try await api.execute(
HTTPRequest(
url: configuration.url.appendingPathComponent("admin/oauth/clients/\(clientId)"),
method: .delete
)
)
.decoded(decoder: configuration.decoder)
}

/// Regenerates the secret for an OAuth client.
/// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
///
/// - Parameter clientId: The unique identifier of the OAuth client.
/// - Note: This function should only be called on a server. Never expose your `service_role` key in the browser.
@discardableResult
public func regenerateClientSecret(clientId: String) async throws -> OAuthClient {
try await api.execute(
HTTPRequest(
url: configuration.url
.appendingPathComponent("admin/oauth/clients/\(clientId)/regenerate_secret"),
method: .post
)
)
.decoded(decoder: configuration.decoder)
}
}
103 changes: 103 additions & 0 deletions Sources/Auth/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,7 @@
self.phone = phone
self.password = password
self.nonce = nonce
self.emailChangeToken = emailChangeToken

Check warning on line 505 in Sources/Auth/Types.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (macOS legacy) (test, MACOS, 15.4)

'emailChangeToken' is deprecated: This is an old field, stop relying on it.

Check warning on line 505 in Sources/Auth/Types.swift

View workflow job for this annotation

GitHub Actions / xcodebuild (macOS legacy) (test, MAC_CATALYST, 15.4)

'emailChangeToken' is deprecated: This is an old field, stop relying on it.
self.data = data
}
}
Expand Down Expand Up @@ -1027,3 +1027,106 @@
// public static let emailChangeCurrent = GenerateLinkType(rawValue: "email_change_current")
// public static let emailChangeNew = GenerateLinkType(rawValue: "email_change_new")
//}

// MARK: - OAuth Client Types

/// OAuth client grant types supported by the OAuth 2.1 server.
/// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
public enum OAuthClientGrantType: String, Codable, Hashable, Sendable {
case authorizationCode = "authorization_code"
case refreshToken = "refresh_token"
}

/// OAuth client response types supported by the OAuth 2.1 server.
/// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
public enum OAuthClientResponseType: String, Codable, Hashable, Sendable {
case code
}

/// OAuth client type indicating whether the client can keep credentials confidential.
/// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
public enum OAuthClientType: String, Codable, Hashable, Sendable {
case `public`
case confidential
}

/// OAuth client registration type.
/// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
public enum OAuthClientRegistrationType: String, Codable, Hashable, Sendable {
case dynamic
case manual
}

/// OAuth client object returned from the OAuth 2.1 server.
/// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
public struct OAuthClient: Codable, Hashable, Sendable {
/// Unique identifier for the OAuth client
public let clientId: String
/// Human-readable name of the OAuth client
public let clientName: String
/// Client secret (only returned on registration and regeneration)
public let clientSecret: String?
/// Type of OAuth client
public let clientType: OAuthClientType
/// Token endpoint authentication method
public let tokenEndpointAuthMethod: String
/// Registration type of the client
public let registrationType: OAuthClientRegistrationType
/// URI of the OAuth client
public let clientUri: String?
/// Array of allowed redirect URIs
public let redirectUris: [String]
/// Array of allowed grant types
public let grantTypes: [OAuthClientGrantType]
/// Array of allowed response types
public let responseTypes: [OAuthClientResponseType]
/// Scope of the OAuth client
public let scope: String?
/// Timestamp when the client was created
public let createdAt: Date
/// Timestamp when the client was last updated
public let updatedAt: Date
}

/// Parameters for creating a new OAuth client.
/// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
public struct CreateOAuthClientParams: Encodable, Hashable, Sendable {
/// Human-readable name of the OAuth client
public let clientName: String
/// URI of the OAuth client
public let clientUri: String?
/// Array of allowed redirect URIs
public let redirectUris: [String]
/// Array of allowed grant types (optional, defaults to authorization_code and refresh_token)
public let grantTypes: [OAuthClientGrantType]?
/// Array of allowed response types (optional, defaults to code)
public let responseTypes: [OAuthClientResponseType]?
/// Scope of the OAuth client
public let scope: String?

public init(
clientName: String,
clientUri: String? = nil,
redirectUris: [String],
grantTypes: [OAuthClientGrantType]? = nil,
responseTypes: [OAuthClientResponseType]? = nil,
scope: String? = nil
) {
self.clientName = clientName
self.clientUri = clientUri
self.redirectUris = redirectUris
self.grantTypes = grantTypes
self.responseTypes = responseTypes
self.scope = scope
}
}

/// Response type for listing OAuth clients.
/// Only relevant when the OAuth 2.1 server is enabled in Supabase Auth.
public struct ListOAuthClientsPaginatedResponse: Hashable, Sendable {
public let clients: [OAuthClient]
public let aud: String
public var nextPage: Int?
public var lastPage: Int
public var total: Int
}
Loading
Loading