Skip to content

Commit

Permalink
Add sign in with id token method (#49)
Browse files Browse the repository at this point in the history
* Add sign in with id token method

* Add test

* ci: copy secrets file

* Format and fix tests

* Add deprecations

* Fix initializer
  • Loading branch information
grdsdev committed Apr 10, 2023
1 parent b4aa56b commit e36a47a
Show file tree
Hide file tree
Showing 11 changed files with 123 additions and 78 deletions.
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"
}
}

0 comments on commit e36a47a

Please sign in to comment.