Skip to content

Commit

Permalink
VIT-4754: Vital Connect private API for sign-in tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
andersio committed Oct 17, 2023
1 parent 0f5a7e7 commit 8309624
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ public extension VitalClient {
}

public extension VitalClient.Link {


@available(*, deprecated)
struct ExchangedCredentials: Decodable {
public struct Team: Decodable {
public let name: String
Expand All @@ -29,7 +30,8 @@ public extension VitalClient.Link {
public let environment: String
public let team: Team
}


@available(*, deprecated)
static func exchangeCode(code: String, environment: Environment) async throws -> ExchangedCredentials {
let client = makeClient(environment: environment)
let request: Request<ExchangedCredentials> = .init(path: "/v2/link/code/exchange", method: .post, query: [("code", code)])
Expand Down
97 changes: 97 additions & 0 deletions Sources/VitalCore/Core/Client/VitalClient+VitalConnect.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import Foundation

// APIs intended for Vital Connect apps
// Does not appear under normal iOS SDK imports.

extension VitalClient {
@_spi(VitalConnectApp)
public static func signInWithInviteCode(_ code: String) async throws -> InviteCodeMetadata {
let environment = try detectEnvironment(fromCode: code)
let credentials = try await exchangeInviteCode(code: code, environment: environment)
try await VitalClient.signIn(withRawToken: credentials.signInToken)
return InviteCodeMetadata(
team: credentials.team,
userId: credentials.userId,
environment: environment
)
}

@_spi(VitalConnectApp)
public static func revokeLegacyApiKey(_ key: String, forUserId userId: String, in environment: Environment) async throws {
try await revokeApiKey(key, forUserId: userId, in: environment)
}
}

@_spi(VitalConnectApp)
public struct InviteCodeMetadata {
public let team: Team
public let userId: String
public let environment: Environment

public struct Team: Decodable {
public let name: String
public let logoUrl: URL?
}
}

@_spi(VitalConnectApp)
public struct VitalInviteCodeError: Error, CustomStringConvertible {
public let description: String

public init(_ description: String) {
self.description = description
}
}

internal struct ExchangedCredentials: Decodable {
let userId: String
let signInToken: String
let team: InviteCodeMetadata.Team
}

internal func exchangeInviteCode(code: String, environment: Environment) async throws -> ExchangedCredentials {
let client = makeClient(
environment: environment,
delegate: VitalBaseClientDelegate()
) { config in
config.sessionConfiguration = .ephemeral
}
let request: Request<ExchangedCredentials> = .init(
path: "/v2/link/code/exchange",
method: .post,
query: [("code", code), ("grant_type", "sign_in_token")]
)

return try await client.send(request).value
}

internal func revokeApiKey(_ key: String, forUserId userId: String, in environment: Environment) async throws {
let client = makeClient(
environment: environment,
delegate: VitalClientDelegate(environment: environment, authStrategy: .apiKey(key))
) { config in
config.sessionConfiguration = .ephemeral
}
let request: Request<Void> = .init(path: "/v2/user/\(userId)/revoke_app_api_key", method: .post)
try await client.send(request)
}

internal func detectEnvironment(fromCode code: String) throws -> Environment {
guard code.count >= 4 else { throw VitalInviteCodeError("Code has invalid prefix") }

let prefix = code.prefix(4)
let environment = String(prefix.prefix(2))
guard let region = Environment.Region(rawValue: String(prefix.suffix(2))) else {
throw VitalInviteCodeError("Unrecognized region")
}

switch environment {
case "sk":
return .sandbox(region)
case "pk":
return .production(region)
default:
throw VitalInviteCodeError("Unrecognized environment")
}
}

0 comments on commit 8309624

Please sign in to comment.