Skip to content

Commit a43d47e

Browse files
committed
add tests for admin features
1 parent 66b6501 commit a43d47e

File tree

8 files changed

+385
-56
lines changed

8 files changed

+385
-56
lines changed

Sources/Auth/AuthAdmin.swift

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,33 @@ public struct AuthAdmin: Sendable {
167167
return pagination
168168
}
169169

170-
public func generateLink() {
171-
170+
/// Generates email links and OTPs to be sent via a custom email provider.
171+
///
172+
/// - Parameter params: The parameters for the link generation.
173+
/// - Throws: An error if the link generation fails.
174+
/// - Returns: The generated link.
175+
public func generateLink(params: GenerateLinkParams) async throws -> GenerateLinkResponse {
176+
let response = try await api.execute(
177+
HTTPRequest(
178+
url: configuration.url.appendingPathComponent("admin/generate_link").appendingQueryItems(
179+
[
180+
(params.redirectTo ?? configuration.redirectToURL).map {
181+
URLQueryItem(
182+
name: "redirect_to",
183+
value: $0.absoluteString
184+
)
185+
}
186+
].compactMap { $0 }
187+
),
188+
method: .post,
189+
body: encoder.encode(params.body)
190+
)
191+
).decoded(as: AnyJSON.self, decoder: configuration.decoder)
192+
193+
let properties = try response.decode(as: GenerateLinkProperties.self)
194+
let user = try response.decode(as: User.self)
195+
196+
return GenerateLinkResponse(properties: properties, user: user)
172197
}
173198
}
174199

Sources/Auth/Internal/APIClient.swift

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Foundation
2-
import Helpers
32
import HTTPTypes
3+
import Helpers
44

55
extension HTTPClient {
66
init(configuration: AuthClient.Configuration) {
@@ -12,7 +12,7 @@ extension HTTPClient {
1212
interceptors.append(
1313
RetryRequestInterceptor(
1414
retryableHTTPMethods: RetryRequestInterceptor.defaultRetryableHTTPMethods.union(
15-
[.post] // Add POST method so refresh token are also retried.
15+
[.post] // Add POST method so refresh token are also retried.
1616
)
1717
)
1818
)
@@ -42,7 +42,7 @@ struct APIClient: Sendable {
4242

4343
let response = try await http.send(request)
4444

45-
guard 200 ..< 300 ~= response.statusCode else {
45+
guard 200..<300 ~= response.statusCode else {
4646
throw handleError(response: response)
4747
}
4848

@@ -64,10 +64,12 @@ struct APIClient: Sendable {
6464
}
6565

6666
func handleError(response: Helpers.HTTPResponse) -> AuthError {
67-
guard let error = try? response.decoded(
68-
as: _RawAPIErrorResponse.self,
69-
decoder: configuration.decoder
70-
) else {
67+
guard
68+
let error = try? response.decoded(
69+
as: _RawAPIErrorResponse.self,
70+
decoder: configuration.decoder
71+
)
72+
else {
7173
return .api(
7274
message: "Unexpected error",
7375
errorCode: .unexpectedFailure,
@@ -78,11 +80,14 @@ struct APIClient: Sendable {
7880

7981
let responseAPIVersion = parseResponseAPIVersion(response)
8082

81-
let errorCode: ErrorCode? = if let responseAPIVersion, responseAPIVersion >= apiVersions[._20240101]!.timestamp, let code = error.code {
82-
ErrorCode(code)
83-
} else {
84-
error.errorCode
85-
}
83+
let errorCode: ErrorCode? =
84+
if let responseAPIVersion, responseAPIVersion >= apiVersions[._20240101]!.timestamp,
85+
let code = error.code
86+
{
87+
ErrorCode(code)
88+
} else {
89+
error.errorCode
90+
}
8691

8792
if errorCode == nil, let weakPassword = error.weakPassword {
8893
return .weakPassword(

Sources/Auth/Types.swift

Lines changed: 121 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,31 @@ public struct User: Codable, Hashable, Identifiable, Sendable {
143143
public var isAnonymous: Bool
144144
public var factors: [Factor]?
145145

146+
enum CodingKeys: String, CodingKey {
147+
case id
148+
case appMetadata = "app_metadata"
149+
case userMetadata = "user_metadata"
150+
case aud
151+
case confirmationSentAt = "confirmation_sent_at"
152+
case recoverySentAt = "recovery_sent_at"
153+
case emailChangeSentAt = "email_change_sent_at"
154+
case newEmail = "new_email"
155+
case invitedAt = "invited_at"
156+
case actionLink = "action_link"
157+
case email
158+
case phone
159+
case createdAt = "created_at"
160+
case confirmedAt = "confirmed_at"
161+
case emailConfirmedAt = "email_confirmed_at"
162+
case phoneConfirmedAt = "phone_confirmed_at"
163+
case lastSignInAt = "last_sign_in_at"
164+
case role
165+
case updatedAt = "updated_at"
166+
case identities
167+
case isAnonymous = "is_anonymous"
168+
case factors
169+
}
170+
146171
public init(
147172
id: UUID,
148173
appMetadata: [String: AnyJSON],
@@ -229,6 +254,17 @@ public struct UserIdentity: Codable, Hashable, Identifiable, Sendable {
229254
public var lastSignInAt: Date?
230255
public var updatedAt: Date?
231256

257+
enum CodingKeys: String, CodingKey {
258+
case id
259+
case identityId = "identity_id"
260+
case userId = "user_id"
261+
case identityData = "identity_data"
262+
case provider
263+
case createdAt = "created_at"
264+
case lastSignInAt = "last_sign_in_at"
265+
case updatedAt = "updated_at"
266+
}
267+
232268
public init(
233269
id: String,
234270
identityId: UUID,
@@ -249,17 +285,6 @@ public struct UserIdentity: Codable, Hashable, Identifiable, Sendable {
249285
self.updatedAt = updatedAt
250286
}
251287

252-
private enum CodingKeys: CodingKey {
253-
case id
254-
case identityId
255-
case userId
256-
case identityData
257-
case provider
258-
case createdAt
259-
case lastSignInAt
260-
case updatedAt
261-
}
262-
263288
public init(from decoder: any Decoder) throws {
264289
let container = try decoder.container(keyedBy: CodingKeys.self)
265290

@@ -909,64 +934,126 @@ public struct ListUsersPaginatedResponse: Hashable, Sendable {
909934
public var total: Int
910935
}
911936

912-
public struct GenerateLinkParams {
913-
var type: String
914-
var email: String
915-
var password: String?
916-
var newEmail: String?
917-
var data: [String: AnyJSON]?
937+
public struct GenerateLinkParams: Sendable {
938+
struct Body: Encodable {
939+
var type: GenerateLinkType
940+
var email: String
941+
var password: String?
942+
var newEmail: String?
943+
var data: [String: AnyJSON]?
944+
}
945+
var body: Body
918946
var redirectTo: URL?
919947

948+
/// Generates a signup link.
920949
public static func signUp(
921950
email: String,
922951
password: String,
923952
data: [String: AnyJSON]? = nil,
924953
redirectTo: URL? = nil
925954
) -> GenerateLinkParams {
926955
GenerateLinkParams(
927-
type: "signup",
928-
email: email,
929-
password: password,
930-
data: data,
956+
body: .init(
957+
type: .signup,
958+
email: email,
959+
password: password,
960+
data: data
961+
),
931962
redirectTo: redirectTo
932963
)
933964
}
934965

966+
/// Generates an invite link.
935967
public static func invite(
936968
email: String,
937-
data: [String: AnyJSON]?,
938-
redirectTo: URL?
969+
data: [String: AnyJSON]? = nil,
970+
redirectTo: URL? = nil
939971
) -> GenerateLinkParams {
940972
GenerateLinkParams(
941-
type: "invite",
942-
email: email,
943-
data: data,
973+
body: .init(
974+
type: .invite,
975+
email: email,
976+
data: data
977+
),
944978
redirectTo: redirectTo
945979
)
946980
}
947981

982+
/// Generates a magic link.
948983
public static func magicLink(
949984
email: String,
950-
data: [String: AnyJSON]?,
951-
redirectTo: URL?
985+
data: [String: AnyJSON]? = nil,
986+
redirectTo: URL? = nil
952987
) -> GenerateLinkParams {
953988
GenerateLinkParams(
954-
type: "magiclink",
955-
email: email,
956-
data: data,
989+
body: .init(
990+
type: .magiclink,
991+
email: email,
992+
data: data
993+
),
957994
redirectTo: redirectTo
958995
)
959996
}
960997

998+
/// Generates a recovery link.
961999
public static func recovery(
9621000
email: String,
963-
redirectTo: URL?
1001+
redirectTo: URL? = nil
9641002
) -> GenerateLinkParams {
9651003
GenerateLinkParams(
966-
type: "recovery",
967-
email: email,
1004+
body: .init(
1005+
type: .recovery,
1006+
email: email
1007+
),
9681008
redirectTo: redirectTo
9691009
)
9701010
}
9711011

9721012
}
1013+
1014+
/// The response from the `generateLink` function.
1015+
public struct GenerateLinkResponse: Hashable, Sendable {
1016+
/// The properties related to the email link generated.
1017+
public let properties: GenerateLinkProperties
1018+
/// The user that the email link is associated to.
1019+
public let user: User
1020+
}
1021+
1022+
/// The properties related to the email link generated.
1023+
public struct GenerateLinkProperties: Decodable, Hashable, Sendable {
1024+
/// The email link to send to the users.
1025+
/// The action link follows the following format: auth/v1/verify?type={verification_type}&token={hashed_token}&redirect_to={redirect_to}
1026+
public let actionLink: URL
1027+
/// The raw ramil OTP.
1028+
/// You should send this in the email if you want your users to verify using an OTP instead of the action link.
1029+
public let emailOTP: String
1030+
/// The hashed token appended to the action link.
1031+
public let hashedToken: String
1032+
/// The URL appended to the action link.
1033+
public let redirectTo: URL
1034+
/// The verification type that the emaillink is associated to.
1035+
public let verificationType: GenerateLinkType
1036+
1037+
enum CodingKeys: String, CodingKey {
1038+
case actionLink = "action_link"
1039+
case emailOTP = "email_otp"
1040+
case hashedToken = "hashed_token"
1041+
case redirectTo = "redirect_to"
1042+
case verificationType = "verification_type"
1043+
}
1044+
}
1045+
1046+
public struct GenerateLinkType: RawRepresentable, Codable, Hashable, Sendable {
1047+
public let rawValue: String
1048+
1049+
public init(rawValue: String) {
1050+
self.rawValue = rawValue
1051+
}
1052+
1053+
public static let signup = GenerateLinkType(rawValue: "signup")
1054+
public static let invite = GenerateLinkType(rawValue: "invite")
1055+
public static let magiclink = GenerateLinkType(rawValue: "magiclink")
1056+
public static let recovery = GenerateLinkType(rawValue: "recovery")
1057+
public static let emailChangeCurrent = GenerateLinkType(rawValue: "email_change_current")
1058+
public static let emailChangeNew = GenerateLinkType(rawValue: "email_change_new")
1059+
}

0 commit comments

Comments
 (0)