mirror of
https://github.com/stack-auth/stack.git
synced 2026-06-13 21:01:21 +08:00
### Summary of Changes Previously, on the Swift SDK, the `signInWithOAuth` function wasn't working. In this PR, we fix it by having the `getOAuthUrl` function to actually redirect correctly. Note that to do so, we updated the `validRedirectUrl` check on the backend to accept app native redirects (from our new trusted url scheme). Another thing to note is that we added functionality to the `TokenStore` abstraction to conditionally refresh the access token that the user is trying to fetch if it is expired/close to expiring if possible. `getOAuthUrl` will attempt to get a valid access token, and thus will rely on our algorithm documented in `utilities.md`. The specs serve as the source of truth. We go further and implement Apple Native sign in. To do so, we have it hit a new route on the backend and verify the `jwtToken` retrieved by the sdk against an Apple-provided set of `jwks`. We use jose to do so, in line with the rest of the codebase. We take this opportunity to refactor the oauth provider route owing to the amount of duplicated logic. Additionally, to enable the apple sign in, users will have to update the Apple authentication method modal on the dashboard and add accepted bundle ids. These are identifiers for projects, and we will check the `JWT` on the backend to make sure the audience is set to an accepted bundleId. We also update the Apple modal to be more informative. ### Using the new Features To use the Apple native sign in, users will have to 1) sign up with an apple developer account, 2) set up their bundleids for their projects by connecting them to the apple developer account, 3) update the Stack-Auth Authentication Methods dashboard apple modal with the relevant fields. Then, trying to sign in with apple with our Swift SDK will use the apple native sign in. ### UI Changes Renamed the fields in the apple modal. Added a new field for bundle ids. See below. https://github.com/user-attachments/assets/0e760c0e-3198-4818-ac7f-4900d7a125bb Co-authored-by: Konstantin Wohlwend <n2d4xc@gmail.com>
183 lines
5.8 KiB
Swift
183 lines
5.8 KiB
Swift
import Testing
|
|
import Foundation
|
|
@testable import StackAuth
|
|
|
|
@Suite("Contact Channel Tests")
|
|
struct ContactChannelTests {
|
|
|
|
// MARK: - List Contact Channels Tests
|
|
|
|
@Test("Should list contact channels after sign up")
|
|
func listContactChannelsAfterSignUp() async throws {
|
|
let app = TestConfig.createClientApp()
|
|
let email = TestConfig.uniqueEmail()
|
|
|
|
try await app.signUpWithCredential(email: email, password: TestConfig.testPassword)
|
|
|
|
let user = try await app.getUser()
|
|
let channels = try await user?.listContactChannels() ?? []
|
|
|
|
// Should have at least the primary email
|
|
#expect(!channels.isEmpty)
|
|
|
|
// Find the primary email channel
|
|
var primaryChannel: ContactChannel? = nil
|
|
for channel in channels {
|
|
let channelValue = await channel.value
|
|
let channelIsPrimary = await channel.isPrimary
|
|
if channelValue == email && channelIsPrimary {
|
|
primaryChannel = channel
|
|
break
|
|
}
|
|
}
|
|
#expect(primaryChannel != nil)
|
|
}
|
|
|
|
@Test("Should have correct contact channel properties")
|
|
func contactChannelProperties() async throws {
|
|
let app = TestConfig.createClientApp()
|
|
let email = TestConfig.uniqueEmail()
|
|
|
|
try await app.signUpWithCredential(email: email, password: TestConfig.testPassword)
|
|
|
|
let user = try await app.getUser()
|
|
let channels = try await user?.listContactChannels() ?? []
|
|
|
|
guard let channel = channels.first else {
|
|
Issue.record("Expected at least one contact channel")
|
|
return
|
|
}
|
|
|
|
let channelId = channel.id // nonisolated, no await needed
|
|
let channelType = await channel.type
|
|
let channelValue = await channel.value
|
|
|
|
#expect(!channelId.isEmpty)
|
|
#expect(channelType == "email")
|
|
#expect(!channelValue.isEmpty)
|
|
}
|
|
|
|
@Test("Should identify primary contact channel")
|
|
func identifyPrimaryContactChannel() async throws {
|
|
let app = TestConfig.createClientApp()
|
|
let email = TestConfig.uniqueEmail()
|
|
|
|
try await app.signUpWithCredential(email: email, password: TestConfig.testPassword)
|
|
|
|
let user = try await app.getUser()
|
|
let channels = try await user?.listContactChannels() ?? []
|
|
|
|
// Count primary channels
|
|
var primaryCount = 0
|
|
var primaryValue: String? = nil
|
|
for channel in channels {
|
|
let isPrimary = await channel.isPrimary
|
|
if isPrimary {
|
|
primaryCount += 1
|
|
primaryValue = await channel.value
|
|
}
|
|
}
|
|
|
|
#expect(primaryCount == 1)
|
|
#expect(primaryValue == email)
|
|
}
|
|
|
|
// MARK: - Contact Channel via Server
|
|
|
|
@Test("Should list contact channels via server")
|
|
func listContactChannelsViaServer() async throws {
|
|
let app = TestConfig.createServerApp()
|
|
let email = TestConfig.uniqueEmail()
|
|
|
|
let user = try await app.createUser(email: email)
|
|
|
|
let channels = try await user.listContactChannels()
|
|
|
|
#expect(!channels.isEmpty)
|
|
|
|
// Find the email channel
|
|
var foundChannel: ContactChannel? = nil
|
|
for channel in channels {
|
|
let channelValue = await channel.value
|
|
if channelValue == email {
|
|
foundChannel = channel
|
|
break
|
|
}
|
|
}
|
|
#expect(foundChannel != nil)
|
|
|
|
// Clean up
|
|
try await user.delete()
|
|
}
|
|
|
|
@Test("Should handle user with no contact channels")
|
|
func userWithNoContactChannels() async throws {
|
|
let app = TestConfig.createServerApp()
|
|
|
|
// Create user without email
|
|
let user = try await app.createUser(displayName: "No Email User")
|
|
|
|
let channels = try await user.listContactChannels()
|
|
|
|
// Should be empty
|
|
#expect(channels.isEmpty)
|
|
|
|
// Clean up
|
|
try await user.delete()
|
|
}
|
|
|
|
@Test("Should show verified status correctly")
|
|
func verifiedStatusCorrect() async throws {
|
|
let app = TestConfig.createServerApp()
|
|
let email = TestConfig.uniqueEmail()
|
|
|
|
// Create user with verified email
|
|
let user = try await app.createUser(email: email, primaryEmailVerified: true)
|
|
|
|
let channels = try await user.listContactChannels()
|
|
|
|
// Find the email channel
|
|
var emailChannel: ContactChannel? = nil
|
|
for channel in channels {
|
|
let channelValue = await channel.value
|
|
if channelValue == email {
|
|
emailChannel = channel
|
|
break
|
|
}
|
|
}
|
|
|
|
let isVerified = await emailChannel?.isVerified
|
|
#expect(isVerified == true)
|
|
|
|
// Clean up
|
|
try await user.delete()
|
|
}
|
|
|
|
@Test("Should show unverified status correctly")
|
|
func unverifiedStatusCorrect() async throws {
|
|
let app = TestConfig.createServerApp()
|
|
let email = TestConfig.uniqueEmail()
|
|
|
|
// Create user with unverified email (default)
|
|
let user = try await app.createUser(email: email, primaryEmailVerified: false)
|
|
|
|
let channels = try await user.listContactChannels()
|
|
|
|
// Find the email channel
|
|
var emailChannel: ContactChannel? = nil
|
|
for channel in channels {
|
|
let channelValue = await channel.value
|
|
if channelValue == email {
|
|
emailChannel = channel
|
|
break
|
|
}
|
|
}
|
|
|
|
let isVerified = await emailChannel?.isVerified
|
|
#expect(isVerified == false)
|
|
|
|
// Clean up
|
|
try await user.delete()
|
|
}
|
|
}
|