1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322 | // Generated by scripts/gen-api.py from spec/openapi.json — do not edit.
import Foundation
// MARK: - Types
/// Values this build does not know decode as `.unrecognized`.
public enum GrantScope: RawRepresentable, Codable, Sendable, Hashable {
case dns
case tunnel
case ipfs
case vps
/// A value the server sent that this version of the package does not list.
case unrecognized(String)
/// Every value the spec listed when this file was generated.
public static let known: [GrantScope] = [.dns, .tunnel, .ipfs, .vps]
public init(rawValue: String) {
switch rawValue {
case "dns": self = .dns
case "tunnel": self = .tunnel
case "ipfs": self = .ipfs
case "vps": self = .vps
default: self = .unrecognized(rawValue)
}
}
public var rawValue: String {
switch self {
case .dns: return "dns"
case .tunnel: return "tunnel"
case .ipfs: return "ipfs"
case .vps: return "vps"
case .unrecognized(let raw): return raw
}
}
public init(from decoder: any Decoder) throws {
self.init(rawValue: try decoder.singleValueContainer().decode(String.self))
}
public func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(rawValue)
}
}
/// Values this build does not know decode as `.unrecognized`.
public enum GrantState: RawRepresentable, Codable, Sendable, Hashable {
case pending
case active
case revoked
case declined
case left
/// A value the server sent that this version of the package does not list.
case unrecognized(String)
/// Every value the spec listed when this file was generated.
public static let known: [GrantState] = [.pending, .active, .revoked, .declined, .left]
public init(rawValue: String) {
switch rawValue {
case "pending": self = .pending
case "active": self = .active
case "revoked": self = .revoked
case "declined": self = .declined
case "left": self = .left
default: self = .unrecognized(rawValue)
}
}
public var rawValue: String {
switch self {
case .pending: return "pending"
case .active: return "active"
case .revoked: return "revoked"
case .declined: return "declined"
case .left: return "left"
case .unrecognized(let raw): return raw
}
}
public init(from decoder: any Decoder) throws {
self.init(rawValue: try decoder.singleValueContainer().decode(String.self))
}
public func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(rawValue)
}
}
/// Values this build does not know decode as `.unrecognized`.
public enum GrantPayer: RawRepresentable, Codable, Sendable, Hashable {
case owner
case grantee
case split
/// A value the server sent that this version of the package does not list.
case unrecognized(String)
/// Every value the spec listed when this file was generated.
public static let known: [GrantPayer] = [.owner, .grantee, .split]
public init(rawValue: String) {
switch rawValue {
case "owner": self = .owner
case "grantee": self = .grantee
case "split": self = .split
default: self = .unrecognized(rawValue)
}
}
public var rawValue: String {
switch self {
case .owner: return "owner"
case .grantee: return "grantee"
case .split: return "split"
case .unrecognized(let raw): return raw
}
}
public init(from decoder: any Decoder) throws {
self.init(rawValue: try decoder.singleValueContainer().decode(String.self))
}
public func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(rawValue)
}
}
/// Shared by 7 operations.
public struct Grant: Codable, Sendable, Hashable, Identifiable {
public var id: String
public var registrationId: String
public var scopes: [GrantScope]
public var state: GrantState
public var invitedEmail: String
/// Who is billed for usage under the grant — the domain owner (the default), the grantee, or split (each charge booked half to each). Fixed at invite time; resources a delegate creates live on the owner's account in every mode, and IPFS gateway egress bills the owner regardless.
public var payer: GrantPayer
/// True on a pending/active grant whose registration expired, transferred out or changed owner — it confers nothing until re-granted.
public var inactive: Bool
public var createdAt: String
public var acceptedAt: String?
public var revokedAt: String?
public var domain: String?
public var ownerEmail: String?
public init(id: String, registrationId: String, scopes: [GrantScope], state: GrantState, invitedEmail: String, payer: GrantPayer, inactive: Bool, createdAt: String, acceptedAt: String? = nil, revokedAt: String? = nil, domain: String? = nil, ownerEmail: String? = nil) {
self.id = id
self.registrationId = registrationId
self.scopes = scopes
self.state = state
self.invitedEmail = invitedEmail
self.payer = payer
self.inactive = inactive
self.createdAt = createdAt
self.acceptedAt = acceptedAt
self.revokedAt = revokedAt
self.domain = domain
self.ownerEmail = ownerEmail
}
enum CodingKeys: String, CodingKey {
case id
case registrationId = "registration_id"
case scopes
case state
case invitedEmail = "invited_email"
case payer
case inactive
case createdAt = "created_at"
case acceptedAt = "accepted_at"
case revokedAt = "revoked_at"
case domain
case ownerEmail = "owner_email"
}
/// Explicit so required-but-nullable fields encode as `null` rather than vanishing.
public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(registrationId, forKey: .registrationId)
try container.encode(scopes, forKey: .scopes)
try container.encode(state, forKey: .state)
try container.encode(invitedEmail, forKey: .invitedEmail)
try container.encode(payer, forKey: .payer)
try container.encode(inactive, forKey: .inactive)
try container.encode(createdAt, forKey: .createdAt)
try container.encode(acceptedAt, forKey: .acceptedAt)
try container.encode(revokedAt, forKey: .revokedAt)
try container.encodeIfPresent(domain, forKey: .domain)
try container.encodeIfPresent(ownerEmail, forKey: .ownerEmail)
}
}
/// Shared by 2 operations.
public struct GrantsResponse: Codable, Sendable, Hashable {
public var grants: [Grant]
public init(grants: [Grant]) {
self.grants = grants
}
enum CodingKeys: String, CodingKey {
case grants
}
}
/// Used by POST /domains/registrations/{id}/grants.
public struct InviteBody: Codable, Sendable, Hashable {
public var email: String
public var scopes: [GrantScope]
public var payer: GrantPayer?
public var totpCode: String?
public init(email: String, scopes: [GrantScope], payer: GrantPayer? = nil, totpCode: String? = nil) {
self.email = email
self.scopes = scopes
self.payer = payer
self.totpCode = totpCode
}
enum CodingKeys: String, CodingKey {
case email
case scopes
case payer
case totpCode = "totp_code"
}
}
/// Shared by 5 operations.
public struct GrantResponse: Codable, Sendable, Hashable {
public var grant: Grant
public init(grant: Grant) {
self.grant = grant
}
enum CodingKeys: String, CodingKey {
case grant
}
}
// MARK: - Operations
extension IrdOperation {
public static let sharing_listDomainGrants = IrdOperation(method: .get, pathTemplate: "/domains/registrations/{id}/grants", module: "sharing", name: "listDomainGrants", security: .jwtOnly)
public static let sharing_invite = IrdOperation(method: .post, pathTemplate: "/domains/registrations/{id}/grants", module: "sharing", name: "invite", security: .jwtOnly)
public static let sharing_revokeGrant = IrdOperation(method: .delete, pathTemplate: "/domains/registrations/{id}/grants/{grant-id}", module: "sharing", name: "revokeGrant", security: .jwtOnly)
public static let sharing_listGrants = IrdOperation(method: .get, pathTemplate: "/grants", module: "sharing", name: "listGrants", security: .jwtOnly)
public static let sharing_acceptGrant = IrdOperation(method: .post, pathTemplate: "/grants/{id}/accept", module: "sharing", name: "acceptGrant", security: .jwtOnly)
public static let sharing_declineGrant = IrdOperation(method: .post, pathTemplate: "/grants/{id}/decline", module: "sharing", name: "declineGrant", security: .jwtOnly)
public static let sharing_leaveGrant = IrdOperation(method: .post, pathTemplate: "/grants/{id}/leave", module: "sharing", name: "leaveGrant", security: .jwtOnly)
static let sharingOperations: [IrdOperation] = [.sharing_listDomainGrants, .sharing_invite, .sharing_revokeGrant, .sharing_listGrants, .sharing_acceptGrant, .sharing_declineGrant, .sharing_leaveGrant]
}
/// The `sharing` operations (7).
public struct SharingAPI: Sendable {
let client: IrdClient
/// Who has delegated access to this domain
/// Every grant ever minted on the registration, newest first — pending invites, active grants and ended history. Rows whose validity lapsed (the registration expired, transferred out or changed owner) carry `inactive: true`. Session JWT only.
/// `GET /domains/registrations/{id}/grants` — 200
/// Requires an account session (JWT); API keys answer 401.
public func listDomainGrants(id: String) async throws -> GrantsResponse {
try await client.request(.sharing_listDomainGrants, params: ["id": id])
}
/// Invite another customer to manage parts of this domain
/// Grants `email` (an existing ird account) the listed scopes on this domain — `dns` (hosted-zone records), `tunnel` (tunnels on and under it), `ipfs` (gateway-domain bindings), `vps` (servers anchored to it or a subdomain at create) — pending until they accept.
/// `POST /domains/registrations/{id}/grants` — 201
/// Requires an account session (JWT); API keys answer 401.
public func invite(id: String, body: InviteBody) async throws -> GrantResponse {
try await client.request(.sharing_invite, params: ["id": id], body: body)
}
/// Revoke delegated access
/// Ends a pending or active grant immediately. Deliberately free of the authenticator gate — removing access must never be harder than keeping it. Session JWT only.
/// `DELETE /domains/registrations/{id}/grants/{grant-id}` — 200
/// Requires an account session (JWT); API keys answer 401.
public func revokeGrant(id: String, grantId: String) async throws -> GrantResponse {
try await client.request(.sharing_revokeGrant, params: ["id": id, "grant-id": grantId])
}
/// Domains shared with this account
/// Active grants where this account is the grantee plus pending invites addressed to any email it controls, newest first, with the domain name and the owner's email joined.
/// `GET /grants` — 200
/// Requires an account session (JWT); API keys answer 401.
public func listGrants() async throws -> GrantsResponse {
try await client.request(.sharing_listGrants)
}
/// Accept a delegated-access invite
/// Activates a pending invite addressed to an email this account controls.
/// `POST /grants/{id}/accept` — 200
/// Requires an account session (JWT); API keys answer 401.
public func acceptGrant(id: String) async throws -> GrantResponse {
try await client.request(.sharing_acceptGrant, params: ["id": id])
}
/// Decline a delegated-access invite
/// `POST /grants/{id}/decline` — 200
/// Requires an account session (JWT); API keys answer 401.
public func declineGrant(id: String) async throws -> GrantResponse {
try await client.request(.sharing_declineGrant, params: ["id": id])
}
/// Walk away from an active grant
/// Ends the caller's own grant. On a grantee-pays or split share this also returns hourly billing for resources the delegation anchored fully to the owner — leaving is the grantee's escape hatch even after the grant lapsed.
/// `POST /grants/{id}/leave` — 200
/// Requires an account session (JWT); API keys answer 401.
public func leaveGrant(id: String) async throws -> GrantResponse {
try await client.request(.sharing_leaveGrant, params: ["id": id])
}
}
extension IrdClient {
public var sharing: SharingAPI { SharingAPI(client: self) }
}
|