Skip to content

Commit dd10643

Browse files
authored
Merge pull request #579 from stytchauth/attest
Sessions attest for consumer and b2b
2 parents 6355919 + 00d6de0 commit dd10643

File tree

7 files changed

+221
-0
lines changed

7 files changed

+221
-0
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Generated using Sourcery 2.0.2 — https://github.com/krzysztofzablocki/Sourcery
2+
// DO NOT EDIT
3+
import Combine
4+
import Foundation
5+
6+
public extension StytchB2BClient.Sessions {
7+
/// Exchange an auth token issued by a trusted identity provider for a Stytch session.
8+
/// You must first register a Trusted Auth Token profile in the Stytch dashboard (https://stytch.com/dashboard/trusted-auth-tokens).
9+
/// If a session token or session JWT is provided, it will add the trusted auth token as an authentication factor to the existing session.
10+
func attest(parameters: AttestParameters, completion: @escaping Completion<B2BAuthenticateResponse>) {
11+
Task {
12+
do {
13+
completion(.success(try await attest(parameters: parameters)))
14+
} catch {
15+
completion(.failure(error))
16+
}
17+
}
18+
}
19+
20+
/// Exchange an auth token issued by a trusted identity provider for a Stytch session.
21+
/// You must first register a Trusted Auth Token profile in the Stytch dashboard (https://stytch.com/dashboard/trusted-auth-tokens).
22+
/// If a session token or session JWT is provided, it will add the trusted auth token as an authentication factor to the existing session.
23+
func attest(parameters: AttestParameters) -> AnyPublisher<B2BAuthenticateResponse, Error> {
24+
return Deferred {
25+
Future({ promise in
26+
Task {
27+
do {
28+
promise(.success(try await attest(parameters: parameters)))
29+
} catch {
30+
promise(.failure(error))
31+
}
32+
}
33+
})
34+
}
35+
.eraseToAnyPublisher()
36+
}
37+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Generated using Sourcery 2.0.2 — https://github.com/krzysztofzablocki/Sourcery
2+
// DO NOT EDIT
3+
import Combine
4+
import Foundation
5+
6+
public extension StytchClient.Sessions {
7+
/// Exchange an auth token issued by a trusted identity provider for a Stytch session.
8+
/// You must first register a Trusted Auth Token profile in the Stytch dashboard (https://stytch.com/dashboard/trusted-auth-tokens).
9+
/// If a session token or session JWT is provided, it will add the trusted auth token as an authentication factor to the existing session.
10+
func attest(parameters: AttestParameters, completion: @escaping Completion<AuthenticateResponse>) {
11+
Task {
12+
do {
13+
completion(.success(try await attest(parameters: parameters)))
14+
} catch {
15+
completion(.failure(error))
16+
}
17+
}
18+
}
19+
20+
/// Exchange an auth token issued by a trusted identity provider for a Stytch session.
21+
/// You must first register a Trusted Auth Token profile in the Stytch dashboard (https://stytch.com/dashboard/trusted-auth-tokens).
22+
/// If a session token or session JWT is provided, it will add the trusted auth token as an authentication factor to the existing session.
23+
func attest(parameters: AttestParameters) -> AnyPublisher<AuthenticateResponse, Error> {
24+
return Deferred {
25+
Future({ promise in
26+
Task {
27+
do {
28+
promise(.success(try await attest(parameters: parameters)))
29+
} catch {
30+
promise(.failure(error))
31+
}
32+
}
33+
})
34+
}
35+
.eraseToAnyPublisher()
36+
}
37+
}

Sources/StytchCore/StytchB2BClient/StytchB2BClient+Routes.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ extension StytchB2BClient {
389389
case authenticate
390390
case revoke
391391
case exchange
392+
case attest
392393

393394
var path: Path {
394395
.init(rawValue: rawValue)

Sources/StytchCore/StytchB2BClient/StytchB2BClient+Sessions.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ public extension StytchB2BClient {
4949
}
5050
}
5151

52+
// sourcery: AsyncVariants, (NOTE: - must use /// doc comment styling)
53+
/// Exchange an auth token issued by a trusted identity provider for a Stytch session.
54+
/// You must first register a Trusted Auth Token profile in the Stytch dashboard (https://stytch.com/dashboard/trusted-auth-tokens).
55+
/// If a session token or session JWT is provided, it will add the trusted auth token as an authentication factor to the existing session.
56+
public func attest(parameters: AttestParameters) async throws -> B2BAuthenticateResponse {
57+
try await router.performSessionRequest(to: .attest, parameters: parameters)
58+
}
59+
5260
/// If your app has cookies disabled or simply receives updated session tokens from your backend via means other than
5361
/// `Set-Cookie` headers, you must call this method after receiving the updated tokens to ensure the `StytchClient`
5462
/// and persistent storage are kept up-to-date. You are required to include both the opaque token and the jwt.
@@ -116,3 +124,34 @@ public extension StytchB2BClient.Sessions {
116124
}
117125
}
118126
}
127+
128+
public extension StytchB2BClient.Sessions {
129+
/// The dedicated parameters type for sessions `attest` calls.
130+
struct AttestParameters: Codable, Sendable {
131+
let profileId: String
132+
let token: String
133+
let organizationId: String?
134+
let sessionJwt: String?
135+
let sessionToken: String?
136+
137+
/// - Parameters:
138+
/// - profileId: The member profile identifier to attest.
139+
/// - token: The attestation token issued by the platform.
140+
/// - organizationId: Optional organization ID associated with the session.
141+
/// - sessionJwt: Optional current session JWT.
142+
/// - sessionToken: Optional current session token.
143+
public init(
144+
profileId: String,
145+
token: String,
146+
organizationId: String? = nil,
147+
sessionJwt: String? = nil,
148+
sessionToken: String? = nil
149+
) {
150+
self.profileId = profileId
151+
self.token = token
152+
self.organizationId = organizationId
153+
self.sessionJwt = sessionJwt
154+
self.sessionToken = sessionToken
155+
}
156+
}
157+
}

Sources/StytchCore/StytchClient/StytchClient+Sessions.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public extension StytchClient {
1010
enum SessionsRoute: String, RouteType {
1111
case authenticate
1212
case revoke
13+
case attest
1314

1415
var path: Path {
1516
.init(rawValue: rawValue)
@@ -59,6 +60,14 @@ public extension StytchClient {
5960
}
6061
}
6162

63+
// sourcery: AsyncVariants, (NOTE: - must use /// doc comment styling)
64+
/// Exchange an auth token issued by a trusted identity provider for a Stytch session.
65+
/// You must first register a Trusted Auth Token profile in the Stytch dashboard (https://stytch.com/dashboard/trusted-auth-tokens).
66+
/// If a session token or session JWT is provided, it will add the trusted auth token as an authentication factor to the existing session.
67+
public func attest(parameters: AttestParameters) async throws -> AuthenticateResponse {
68+
try await router.performSessionRequest(to: .attest, parameters: parameters)
69+
}
70+
6271
/// If your app has cookies disabled or simply receives updated session tokens from your backend via means other than
6372
/// `Set-Cookie` headers, you must call this method after receiving the updated tokens to ensure the `StytchClient`
6473
/// and persistent storage are kept up-to-date. You are required to include both the opaque token and the jwt.
@@ -104,3 +113,34 @@ public extension StytchClient.Sessions {
104113
}
105114
}
106115
}
116+
117+
public extension StytchClient.Sessions {
118+
/// The dedicated parameters type for sessions `attest` calls.
119+
struct AttestParameters: Codable, Sendable {
120+
let profileId: String
121+
let token: String
122+
let sessionDurationMinutes: Minutes?
123+
let sessionJwt: String?
124+
let sessionToken: String?
125+
126+
/// - Parameters:
127+
/// - profileId: The profile identifier to attest.
128+
/// - token: The attestation token issued by the platform.
129+
/// - sessionDurationMinutes: Optional override for the requested session duration in minutes.
130+
/// - sessionJwt: Optional current session JWT.
131+
/// - sessionToken: Optional current session token.
132+
public init(
133+
profileId: String,
134+
token: String,
135+
sessionDurationMinutes: Minutes? = nil,
136+
sessionJwt: String? = nil,
137+
sessionToken: String? = nil
138+
) {
139+
self.profileId = profileId
140+
self.token = token
141+
self.sessionDurationMinutes = sessionDurationMinutes
142+
self.sessionJwt = sessionJwt
143+
self.sessionToken = sessionToken
144+
}
145+
}
146+
}

Tests/StytchCoreTests/B2BSessionsTestCase.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,40 @@ final class B2BSessionsTestCase: BaseTestCase {
3434
XCTAssertNotNil(StytchB2BClient.sessions.memberSession)
3535
}
3636

37+
func testSessionsAttest() async throws {
38+
networkInterceptor.responses { B2BAuthenticateResponse.mock }
39+
40+
let parameters = StytchB2BClient.Sessions.AttestParameters(
41+
profileId: "profile_123",
42+
token: "attestation_token",
43+
organizationId: "org_123",
44+
sessionJwt: "existing_jwt",
45+
sessionToken: "existing_token"
46+
)
47+
48+
Current.timer = { _, _, _ in .init() }
49+
50+
XCTAssertNil(StytchB2BClient.sessions.memberSession)
51+
52+
_ = try await StytchB2BClient.sessions.attest(parameters: parameters)
53+
54+
try XCTAssertRequest(
55+
networkInterceptor.requests[0],
56+
urlString: "https://api.stytch.com/sdk/v1/b2b/sessions/attest",
57+
method: .post([
58+
"profile_id": "profile_123",
59+
"token": "attestation_token",
60+
"organization_id": "org_123",
61+
"session_jwt": "existing_jwt",
62+
"session_token": "existing_token",
63+
])
64+
)
65+
66+
XCTAssertEqual(StytchB2BClient.sessions.sessionJwt, .jwt("i'mvalidjson"))
67+
XCTAssertEqual(StytchB2BClient.sessions.sessionToken, .opaque("xyzasdf"))
68+
XCTAssertNotNil(StytchB2BClient.sessions.memberSession)
69+
}
70+
3771
func testSessionsRevoke() async throws {
3872
networkInterceptor.responses { BasicResponse(requestId: "request_id", statusCode: 200) }
3973
Current.timer = { _, _, _ in .init() }

Tests/StytchCoreTests/SessionManagerTestCase.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,39 @@ final class SessionManagerTestCase: BaseTestCase {
4242
XCTAssertNotNil(StytchClient.sessions.session)
4343
}
4444

45+
func testSessionsAttest() async throws {
46+
networkInterceptor.responses { AuthenticateResponse.mock }
47+
let parameters = StytchClient.Sessions.AttestParameters(
48+
profileId: "profile_123",
49+
token: "attestation_token",
50+
sessionDurationMinutes: 30,
51+
sessionJwt: "existing_jwt",
52+
sessionToken: "existing_token"
53+
)
54+
55+
Current.timer = { _, _, _ in .init() }
56+
57+
XCTAssertNil(StytchClient.sessions.session)
58+
59+
_ = try await StytchClient.sessions.attest(parameters: parameters)
60+
61+
try XCTAssertRequest(
62+
networkInterceptor.requests[0],
63+
urlString: "https://api.stytch.com/sdk/v1/sessions/attest",
64+
method: .post([
65+
"profile_id": "profile_123",
66+
"token": "attestation_token",
67+
"session_duration_minutes": 30,
68+
"session_jwt": "existing_jwt",
69+
"session_token": "existing_token",
70+
])
71+
)
72+
73+
XCTAssertEqual(StytchClient.sessions.sessionJwt, .jwt("jwt_for_me"))
74+
XCTAssertEqual(StytchClient.sessions.sessionToken, .opaque("hello_session"))
75+
XCTAssertNotNil(StytchClient.sessions.session)
76+
}
77+
4578
func testSessionsRevoke() async throws {
4679
networkInterceptor.responses { BasicResponse(requestId: "request_id", statusCode: 200) }
4780
Current.timer = { _, _, _ in .init() }

0 commit comments

Comments
 (0)