Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sign in with id token method #49

Merged
merged 6 commits into from
Apr 10, 2023
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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ jobs:
- uses: actions/checkout@v3
- name: Select Xcode ${{ matrix.xcode }}
run: sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app
- name: Copy Secrets file
run: cp Examples/Shared/Sources/_Secrets.swift Examples/Shared/Sources/Secrets.swift
- name: Build example
run: make build-example

Expand Down
1 change: 0 additions & 1 deletion .swift-version

This file was deleted.

1 change: 1 addition & 0 deletions .swiftformat
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
--swiftversion 5.7
--binarygrouping none
--decimalgrouping none
--hexgrouping none
Expand Down
2 changes: 1 addition & 1 deletion Examples/Shared/Sources/AppView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ struct AppView: View {
}
}

func stringfy<T: Codable>(_ value: T) -> String {
func stringfy(_ value: some Codable) -> String {
let encoder = JSONEncoder()
encoder.outputFormatting = [.prettyPrinted, .sortedKeys]
let data = try? encoder.encode(value)
Expand Down
14 changes: 14 additions & 0 deletions Sources/GoTrue/Deprecated.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Foundation

extension GoTrueMetaSecurity {
@available(*, deprecated, renamed: "captchaToken")
public var hcaptchaToken: String {
get { captchaToken }
set { captchaToken = newValue }
}

@available(*, deprecated, renamed: "init(captchaToken:)")
public init(hcaptchaToken: String) {
self.init(captchaToken: hcaptchaToken)
}
}
34 changes: 23 additions & 11 deletions Sources/GoTrue/GoTrueClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public final class GoTrueClient {
email: email,
password: password,
data: data,
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(hcaptchaToken:))
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(captchaToken:))
)
)
)
Expand All @@ -121,7 +121,7 @@ public final class GoTrueClient {
password: password,
phone: phone,
data: data,
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(hcaptchaToken:))
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(captchaToken:))
)
)
)
Expand Down Expand Up @@ -161,6 +161,18 @@ public final class GoTrueClient {
)
}

/// Allows signing in with an ID token issued by certain supported providers.
/// The ID token is verified for validity and a new session is established.
@discardableResult
public func signInWithIdToken(credentials: OpenIDConnectCredentials) async throws -> Session {
try await _signIn(
request: Paths.token.post(
grantType: .idToken,
.openIDConnectCredentials(credentials)
)
)
}

private func _signIn(request: Request<Session>) async throws -> Session {
await Env.sessionManager.remove()

Expand Down Expand Up @@ -200,7 +212,7 @@ public final class GoTrueClient {
email: email,
createUser: shouldCreateUser,
data: data,
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(hcaptchaToken:))
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(captchaToken:))
)
)
)
Expand All @@ -226,7 +238,7 @@ public final class GoTrueClient {
phone: phone,
createUser: shouldCreateUser,
data: data,
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(hcaptchaToken:))
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(captchaToken:))
)
)
)
Expand All @@ -251,11 +263,11 @@ public final class GoTrueClient {
URLQueryItem(name: "provider", value: provider.rawValue),
]

if let scopes = scopes {
if let scopes {
queryItems.append(URLQueryItem(name: "scopes", value: scopes))
}

if let redirectTo = redirectTo {
if let redirectTo {
queryItems.append(URLQueryItem(name: "redirect_to", value: redirectTo.absoluteString))
}

Expand Down Expand Up @@ -383,7 +395,7 @@ public final class GoTrueClient {
)
}

guard let session = session else {
guard let session else {
throw GoTrueError.sessionNotFound
}

Expand All @@ -399,7 +411,7 @@ public final class GoTrueClient {
let session = try? await Env.sessionManager.session()
await Env.sessionManager.remove()

if let session = session {
if let session {
try await Env.client.send(Paths.logout.post.withAuthorization(session.accessToken)).value
}
}
Expand All @@ -420,7 +432,7 @@ public final class GoTrueClient {
email: email,
token: token,
type: type,
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(hcaptchaToken:))
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(captchaToken:))
)
)
)
Expand All @@ -440,7 +452,7 @@ public final class GoTrueClient {
phone: phone,
token: token,
type: type,
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(hcaptchaToken:))
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(captchaToken:))
)
)
)
Expand Down Expand Up @@ -483,7 +495,7 @@ public final class GoTrueClient {
redirectTo: redirectTo,
RecoverParams(
email: email,
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(hcaptchaToken:))
gotrueMetaSecurity: captchaToken.map(GoTrueMetaSecurity.init(captchaToken:))
)
)
).value
Expand Down
2 changes: 1 addition & 1 deletion Sources/GoTrue/GoTrueLocalStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ struct KeychainLocalStorage: GoTrueLocalStorage {
let keychain: Keychain

init(service: String, accessGroup: String?) {
if let accessGroup = accessGroup {
if let accessGroup {
keychain = Keychain(service: service, accessGroup: accessGroup)
} else {
keychain = Keychain(service: service)
Expand Down
2 changes: 1 addition & 1 deletion Sources/GoTrue/Internal/SessionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ private actor LiveSessionManager {
private var task: Task<Session, Error>?

func session() async throws -> Session {
if let task = task {
if let task {
return try await task.value
}

Expand Down
50 changes: 29 additions & 21 deletions Sources/GoTrue/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -301,44 +301,52 @@ public enum Provider: String, Codable, CaseIterable {
}

public struct OpenIDConnectCredentials: Codable, Hashable {
public var idToken: String
public var nonce: String
public var clientID: String?
public var issuer: String?
/// Only Apple and Google ID tokens are supported for use from within iOS or Android applications.
public var provider: Provider?

/// ID token issued by Apple or Google.
public var token: String

/// If the ID token contains a `nonce`, then the hash of this value is compared to the value in
/// the ID token.
public var nonce: String?

/// Verification token received when the user completes the captcha on the site.
public var gotrueMetaSecurity: GoTrueMetaSecurity?

public init(
idToken: String,
nonce: String,
clientID: String? = nil,
issuer: String? = nil,
provider: Provider? = nil
provider: Provider? = nil,
token: String,
nonce: String? = nil,
gotrueMetaSecurity: GoTrueMetaSecurity? = nil
) {
self.idToken = idToken
self.nonce = nonce
self.clientID = clientID
self.issuer = issuer
self.provider = provider
self.token = token
self.nonce = nonce
self.gotrueMetaSecurity = gotrueMetaSecurity
}

public enum CodingKeys: String, CodingKey {
case idToken = "id_token"
case nonce
case clientID = "client_id"
case issuer
case provider
case token
case nonce
case gotrueMetaSecurity = "gotrue_meta_security"
}

public enum Provider: String, Codable, Hashable {
case google, apple
}
}

public struct GoTrueMetaSecurity: Codable, Hashable {
public var hcaptchaToken: String
public var captchaToken: String

public init(hcaptchaToken: String) {
self.hcaptchaToken = hcaptchaToken
public init(captchaToken: String) {
self.captchaToken = captchaToken
}

public enum CodingKeys: String, CodingKey {
case hcaptchaToken = "hcaptcha_token"
case captchaToken = "captcha_token"
}
}

Expand Down
56 changes: 14 additions & 42 deletions Tests/GoTrueTests/GoTrueTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,20 +40,32 @@ final class GoTrueTests: XCTestCase {
func testDecodeSessionOrUser() {
XCTAssertNoThrow(
try JSONDecoder.goTrue.decode(
AuthResponse.self, from: sessionJSON.data(using: .utf8)!
AuthResponse.self, from: json(named: "session")
)
)
}

#if !os(watchOS)
// Not working on watchOS, don't know why.
func test_signUpWithEmailAndPassword() async throws {
Mock.post(path: "signup", json: "signup-response").register()

let user = try await sut.signUp(email: "[email protected]", password: "thepass").user

XCTAssertEqual(user?.email, "[email protected]")
}

func testSignInWithIdToken() async throws {
Mock(
url: URL(string: "http://localhost:54321/auth/v1/token?grant_type=id_token")!,
dataType: .json,
statusCode: 200,
data: [.post: json(named: "session")]
).register()

let session = try await sut
.signInWithIdToken(credentials: OpenIDConnectCredentials(token: "dummy-token-1234"))
XCTAssertEqual(session.user.email, "[email protected]")
}
#endif

func testSignInWithProvider() throws {
Expand Down Expand Up @@ -110,43 +122,3 @@ final class GoTrueTests: XCTestCase {
}
}
}

let sessionJSON = """
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjQ4NjQwMDIxLCJzdWIiOiJmMzNkM2VjOS1hMmVlLTQ3YzQtODBlMS01YmQ5MTlmM2Q4YjgiLCJlbWFpbCI6Imd1aWxoZXJtZTJAZ3Jkcy5kZXYiLCJwaG9uZSI6IiIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIiwicHJvdmlkZXJzIjpbImVtYWlsIl19LCJ1c2VyX21ldGFkYXRhIjp7fSwicm9sZSI6ImF1dGhlbnRpY2F0ZWQifQ.4lMvmz2pJkWu1hMsBgXP98Fwz4rbvFYl4VA9joRv6kY",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "GGduTeu95GraIXQ56jppkw",
"user": {
"id": "f33d3ec9-a2ee-47c4-80e1-5bd919f3d8b8",
"aud": "authenticated",
"role": "authenticated",
"email": "[email protected]",
"email_confirmed_at": "2022-03-30T10:33:41.018575157Z",
"phone": "",
"last_sign_in_at": "2022-03-30T10:33:41.021531328Z",
"app_metadata": {
"provider": "email",
"providers": [
"email"
]
},
"user_metadata": {},
"identities": [
{
"id": "f33d3ec9-a2ee-47c4-80e1-5bd919f3d8b8",
"user_id": "f33d3ec9-a2ee-47c4-80e1-5bd919f3d8b8",
"identity_data": {
"sub": "f33d3ec9-a2ee-47c4-80e1-5bd919f3d8b8"
},
"provider": "email",
"last_sign_in_at": "2022-03-30T10:33:41.015557063Z",
"created_at": "2022-03-30T10:33:41.015612Z",
"updated_at": "2022-03-30T10:33:41.015616Z"
}
],
"created_at": "2022-03-30T10:33:41.005433Z",
"updated_at": "2022-03-30T10:33:41.022688Z"
}
}
"""
37 changes: 37 additions & 0 deletions Tests/GoTrueTests/Resources/session.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhdXRoZW50aWNhdGVkIiwiZXhwIjoxNjQ4NjQwMDIxLCJzdWIiOiJmMzNkM2VjOS1hMmVlLTQ3YzQtODBlMS01YmQ5MTlmM2Q4YjgiLCJlbWFpbCI6Imd1aWxoZXJtZTJAZ3Jkcy5kZXYiLCJwaG9uZSI6IiIsImFwcF9tZXRhZGF0YSI6eyJwcm92aWRlciI6ImVtYWlsIiwicHJvdmlkZXJzIjpbImVtYWlsIl19LCJ1c2VyX21ldGFkYXRhIjp7fSwicm9sZSI6ImF1dGhlbnRpY2F0ZWQifQ.4lMvmz2pJkWu1hMsBgXP98Fwz4rbvFYl4VA9joRv6kY",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "GGduTeu95GraIXQ56jppkw",
"user": {
"id": "f33d3ec9-a2ee-47c4-80e1-5bd919f3d8b8",
"aud": "authenticated",
"role": "authenticated",
"email": "[email protected]",
"email_confirmed_at": "2022-03-30T10:33:41.018575157Z",
"phone": "",
"last_sign_in_at": "2022-03-30T10:33:41.021531328Z",
"app_metadata": {
"provider": "email",
"providers": [
"email"
]
},
"user_metadata": {},
"identities": [
{
"id": "f33d3ec9-a2ee-47c4-80e1-5bd919f3d8b8",
"user_id": "f33d3ec9-a2ee-47c4-80e1-5bd919f3d8b8",
"identity_data": {
"sub": "f33d3ec9-a2ee-47c4-80e1-5bd919f3d8b8"
},
"provider": "email",
"last_sign_in_at": "2022-03-30T10:33:41.015557063Z",
"created_at": "2022-03-30T10:33:41.015612Z",
"updated_at": "2022-03-30T10:33:41.015616Z"
}
],
"created_at": "2022-03-30T10:33:41.005433Z",
"updated_at": "2022-03-30T10:33:41.022688Z"
}
}