api-swift / Sources / IrdAPI / Gate / DomainGate.swift
367 words
 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
import Foundation

/// The gate's state: its rules and the registration id → domain index used to
/// resolve mutations that address a domain by registration id.
public actor DomainGate {
    public nonisolated let rules: GateRules
    private var index: [String: String]

    init(rules: GateRules, index: [String: String]) {
        self.rules = rules
        self.index = index
    }

    public func cachedDomain(for registrationId: String) -> String? { index[registrationId] }
    public var registrationIndex: [String: String] { index }
    func remember(id: String, domain: String) { index[id] = domain }
    func replace(_ newIndex: [String: String]) { index = newIndex }
}

extension IrdClient {
    /// Checks `target` against the gate; a no-op on ungated clients. Public so hand-written
    /// code can gate custom raw calls the same way.
    public func checkGate(op: String, target: GateTarget) async throws {
        guard let gate else { return }
        let domain: String?
        switch target {
        case .domain(let d): domain = d
        case .registrationId(nil): domain = nil
        case .registrationId(let id?): domain = await resolveRegistration(id, gate: gate)
        }
        if let denial = gate.rules.decide(domain) {
            throw IrdError.domainDenied(domain: denial.domain, pattern: denial.pattern, op: op)
        }
    }

    /// Cache hit, else one `GET /domains/registrations/{id}` (failures resolve to nil).
    func resolveRegistration(_ id: String, gate: DomainGate) async -> String? {
        if let cached = await gate.cachedDomain(for: id) { return cached }
        let op = IrdOperation(method: .get, pathTemplate: "/domains/registrations/{id}", module: "registrations", name: "get", security: .keyOrJwt)
        guard let (_, json) = try? await perform(op, params: ["id": id], query: [], body: nil, gated: false),
              let domain = normalizeDomain(json.at(["registration", "domain"])?.stringValue) else { return nil }
        await gate.remember(id: id, domain: domain)
        return domain
    }

    /// Every registration's id → domain, walked with `limit=100` cursor pages.
    func fetchRegistrationIndex() async throws -> [String: String] {
        let op = IrdOperation(method: .get, pathTemplate: "/domains/registrations", module: "registrations", name: "list", security: .keyOrJwt)
        let rows: [JSONValue] = try await Paginator.cursor(
            fetch: { (cursor: String?) in
                var query = [QueryItem("limit", "100")]
                if let cursor { query.append(QueryItem("cursor", cursor)) }
                return try await perform(op, params: [:], query: query, body: nil, gated: false).json
            },
            items: { $0["registrations"]?.arrayValue ?? [] },
            next: { $0["next_cursor"]?.stringValue })
        var index: [String: String] = [:]
        for row in rows {
            if let id = row["id"]?.stringValue, let domain = normalizeDomain(row["domain"]?.stringValue) { index[id] = domain }
        }
        return index
    }
}

static mirror of HEAD · about · clone: git clone https://ird.cod-sursa.ro/api-swift.git