diff --git a/Examples/Examples.xcodeproj/project.pbxproj b/Examples/Examples.xcodeproj/project.pbxproj index 2cb24500..f91f5d37 100644 --- a/Examples/Examples.xcodeproj/project.pbxproj +++ b/Examples/Examples.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 79BD76772B59C3E300CA3D68 /* UserStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD76762B59C3E300CA3D68 /* UserStore.swift */; }; 79BD76792B59C53900CA3D68 /* ChannelStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD76782B59C53900CA3D68 /* ChannelStore.swift */; }; 79BD767B2B59C61300CA3D68 /* MessageStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79BD767A2B59C61300CA3D68 /* MessageStore.swift */; }; + 79C9B8E52BBB16C0003AD942 /* SignInAnonymously.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79C9B8E42BBB16C0003AD942 /* SignInAnonymously.swift */; }; 79D884CA2B3C18830009EA4A /* SlackCloneApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79D884C92B3C18830009EA4A /* SlackCloneApp.swift */; }; 79D884CC2B3C18830009EA4A /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79D884CB2B3C18830009EA4A /* AppView.swift */; }; 79D884CE2B3C18840009EA4A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 79D884CD2B3C18840009EA4A /* Assets.xcassets */; }; @@ -112,6 +113,7 @@ 79BD76762B59C3E300CA3D68 /* UserStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserStore.swift; sourceTree = ""; }; 79BD76782B59C53900CA3D68 /* ChannelStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelStore.swift; sourceTree = ""; }; 79BD767A2B59C61300CA3D68 /* MessageStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageStore.swift; sourceTree = ""; }; + 79C9B8E42BBB16C0003AD942 /* SignInAnonymously.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInAnonymously.swift; sourceTree = ""; }; 79D884C72B3C18830009EA4A /* SlackClone.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SlackClone.app; sourceTree = BUILT_PRODUCTS_DIR; }; 79D884C92B3C18830009EA4A /* SlackCloneApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlackCloneApp.swift; sourceTree = ""; }; 79D884CB2B3C18830009EA4A /* AppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppView.swift; sourceTree = ""; }; @@ -258,6 +260,7 @@ 793E030C2B2DAB5700AC7DED /* SignInWithApple.swift */, 7940E3142B36187A0089BEE1 /* GoogleSignInWithWebFlow.swift */, 79E2B5542B9788BF0042CD21 /* GoogleSignInSDKFlow.swift */, + 79C9B8E42BBB16C0003AD942 /* SignInAnonymously.swift */, ); path = Auth; sourceTree = ""; @@ -505,6 +508,7 @@ 793895CA2954ABFF0044F2B8 /* ExamplesApp.swift in Sources */, 797EFB682BABD90500098D6B /* Stringfy.swift in Sources */, 797EFB6C2BABE1B800098D6B /* FileObjectDetailView.swift in Sources */, + 79C9B8E52BBB16C0003AD942 /* SignInAnonymously.swift in Sources */, 797EFB6A2BABDF3800098D6B /* BucketDetailView.swift in Sources */, 793E030D2B2DAB5700AC7DED /* SignInWithApple.swift in Sources */, 793E030B2B2CEDDA00AC7DED /* ActionState.swift in Sources */, diff --git a/Examples/Examples/Auth/AuthView.swift b/Examples/Examples/Auth/AuthView.swift index dfcfb9c8..ee9ab364 100644 --- a/Examples/Examples/Auth/AuthView.swift +++ b/Examples/Examples/Auth/AuthView.swift @@ -14,6 +14,7 @@ struct AuthView: View { case signInWithApple case googleSignInWebFlow case googleSignInSDKFlow + case signInAnonymously var title: String { switch self { @@ -22,6 +23,7 @@ struct AuthView: View { case .signInWithApple: "Sign in with Apple" case .googleSignInWebFlow: "Google Sign in (Web Flow)" case .googleSignInSDKFlow: "Google Sign in (GIDSignIn SDK Flow)" + case .signInAnonymously: "Sign in Anonymously" } } } @@ -50,6 +52,7 @@ extension AuthView.Option: View { case .signInWithApple: SignInWithApple() case .googleSignInWebFlow: GoogleSignInWithWebFlow() case .googleSignInSDKFlow: GoogleSignInSDKFlow() + case .signInAnonymously: SignInAnonymously() } } } diff --git a/Examples/Examples/Auth/SignInAnonymously.swift b/Examples/Examples/Auth/SignInAnonymously.swift new file mode 100644 index 00000000..39bed36e --- /dev/null +++ b/Examples/Examples/Auth/SignInAnonymously.swift @@ -0,0 +1,27 @@ +// +// SignInAnonymously.swift +// Examples +// +// Created by Guilherme Souza on 01/04/24. +// + +import Supabase +import SwiftUI + +struct SignInAnonymously: View { + var body: some View { + Button("Sign in") { + Task { + do { + try await supabase.auth.signInAnonymously() + } catch { + debug("Error signin in anonymously: \(error)") + } + } + } + } +} + +#Preview { + SignInAnonymously() +} diff --git a/Examples/supabase/config.toml b/Examples/supabase/config.toml index 863a2d45..96344198 100644 --- a/Examples/supabase/config.toml +++ b/Examples/supabase/config.toml @@ -77,6 +77,6 @@ redirect_uri = "" url = "" [auth.external.github] -enabled = true +enabled = false client_id = "12d1131cd3582f942c71" secret = "env(SUPABASE_AUTH_EXTERNAL_GITHUB_SECRET)" \ No newline at end of file diff --git a/Sources/Auth/AuthClient.swift b/Sources/Auth/AuthClient.swift index 03f59a87..da8facfd 100644 --- a/Sources/Auth/AuthClient.swift +++ b/Sources/Auth/AuthClient.swift @@ -362,6 +362,31 @@ public actor AuthClient { ) } + /// Creates a new anonymous user. + /// - Parameters: + /// - data: A custom data object to store the user's metadata. This maps to the + /// `auth.users.raw_user_meta_data` column. The `data` should be a JSON object that includes + /// user-specific info, such as their first and last name. + /// - captchaToken: Verification token received when the user completes the captcha. + @discardableResult + public func signInAnonymously( + data: [String: AnyJSON]? = nil, + captchaToken: String? = nil + ) async throws -> Session { + try await _signIn( + request: Request( + path: "/signup", + method: .post, + body: configuration.encoder.encode( + SignUpRequest( + data: data, + gotrueMetaSecurity: captchaToken.map { AuthMetaSecurity(captchaToken: $0) } + ) + ) + ) + ) + } + private func _signIn(request: Request) async throws -> Session { await sessionManager.remove() @@ -370,10 +395,8 @@ public actor AuthClient { decoder: configuration.decoder ) - if session.user.emailConfirmedAt != nil || session.user.confirmedAt != nil { - try await sessionManager.update(session) - eventEmitter.emit(.signedIn, session: session) - } + try await sessionManager.update(session) + eventEmitter.emit(.signedIn, session: session) return session } diff --git a/Tests/AuthTests/AuthClientTests.swift b/Tests/AuthTests/AuthClientTests.swift index 90bcadf3..78dbda33 100644 --- a/Tests/AuthTests/AuthClientTests.swift +++ b/Tests/AuthTests/AuthClientTests.swift @@ -195,6 +195,61 @@ final class AuthClientTests: XCTestCase { XCTAssertEqual(removeCallCount.value, 1) } + func testSignInAnonymously() async throws { + let emitReceivedEvents = LockIsolated<[(AuthChangeEvent, Session?)]>([]) + + eventEmitter.emit = { @Sendable event, session, _ in + emitReceivedEvents.withValue { + $0.append((event, session)) + } + } + sessionManager.remove = { @Sendable in } + sessionManager.update = { @Sendable _ in } + + api.execute = { @Sendable _ in + .stub( + """ + { + "access_token" : "eyJhbGciOiJIUzI1NiIsImtpZCI6ImpIaU1GZmtNTzRGdVROdXUiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNzExOTk0NzEzLCJpYXQiOjE3MTE5OTExMTMsImlzcyI6Imh0dHBzOi8vYWp5YWdzaHV6bnV2anFoampmdG8uc3VwYWJhc2UuY28vYXV0aC92MSIsInN1YiI6ImJiZmE5MjU0LWM1ZDEtNGNmZi1iYTc2LTU2YmYwM2IwNWEwMSIsImVtYWlsIjoiIiwicGhvbmUiOiIiLCJhcHBfbWV0YWRhdGEiOnt9LCJ1c2VyX21ldGFkYXRhIjp7fSwicm9sZSI6ImF1dGhlbnRpY2F0ZWQiLCJhYWwiOiJhYWwxIiwiYW1yIjpbeyJtZXRob2QiOiJhbm9ueW1vdXMiLCJ0aW1lc3RhbXAiOjE3MTE5OTExMTN9XSwic2Vzc2lvbl9pZCI6ImMyODlmYTcwLWIzYWUtNDI1Yi05MDQxLWUyZjVhNzBlZTcyYSIsImlzX2Fub255bW91cyI6dHJ1ZX0.whBzmyMv3-AQSaiY6Fi-v_G68Q8oULhB7axImj9qOdw", + "expires_at" : 1711994713, + "expires_in" : 3600, + "refresh_token" : "0xS9iJUWdXnWlCJtFiXk5A", + "token_type" : "bearer", + "user" : { + "app_metadata" : { + + }, + "aud" : "authenticated", + "created_at" : "2024-04-01T17:05:13.013312Z", + "email" : "", + "id" : "bbfa9254-c5d1-4cff-ba76-56bf03b05a01", + "identities" : [ + + ], + "is_anonymous" : true, + "last_sign_in_at" : "2024-04-01T17:05:13.018294975Z", + "phone" : "", + "role" : "authenticated", + "updated_at" : "2024-04-01T17:05:13.022041Z", + "user_metadata" : { + + } + } + } + """, + code: 200 + ) + } + + let sut = makeSUT() + + try await sut.signInAnonymously() + + let events = emitReceivedEvents.value.map(\.0) + + XCTAssertEqual(events, [.signedIn]) + } + private func makeSUT() -> AuthClient { let configuration = AuthClient.Configuration( url: clientURL, diff --git a/Tests/AuthTests/RequestsTests.swift b/Tests/AuthTests/RequestsTests.swift index 46d1f6dc..94403100 100644 --- a/Tests/AuthTests/RequestsTests.swift +++ b/Tests/AuthTests/RequestsTests.swift @@ -399,6 +399,17 @@ final class RequestsTests: XCTestCase { } } + func testSignInAnonymously() async { + let sut = makeSUT() + + await assert { + try await sut.signInAnonymously( + data: ["custom_key": .string("custom_value")], + captchaToken: "captcha-token" + ) + } + } + private func assert(_ block: () async throws -> Void) async { do { try await block() diff --git a/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInAnonymously.1.txt b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInAnonymously.1.txt new file mode 100644 index 00000000..68595574 --- /dev/null +++ b/Tests/AuthTests/__Snapshots__/RequestsTests/testSignInAnonymously.1.txt @@ -0,0 +1,7 @@ +curl \ + --request POST \ + --header "Apikey: dummy.api.key" \ + --header "Content-Type: application/json" \ + --header "X-Client-Info: gotrue-swift/x.y.z" \ + --data "{\"data\":{\"custom_key\":\"custom_value\"},\"gotrue_meta_security\":{\"captcha_token\":\"captcha-token\"}}" \ + "http://localhost:54321/auth/v1/signup" \ No newline at end of file