From f6acbe8fe522a358d8d0107de19fea651091a231 Mon Sep 17 00:00:00 2001 From: Nikolay Edigaryev Date: Mon, 9 May 2022 02:46:11 +0300 Subject: [PATCH] Registry: invalidate expired tokens (#55) --- Sources/tart/OCI/Registry.swift | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/Sources/tart/OCI/Registry.swift b/Sources/tart/OCI/Registry.swift index 622f404..992867d 100644 --- a/Sources/tart/OCI/Registry.swift +++ b/Sources/tart/OCI/Registry.swift @@ -9,6 +9,12 @@ enum RegistryError: Error { struct TokenResponse: Decodable { var token: String + var expiresIn: Int? + + enum CodingKeys: String, CodingKey { + case token + case expiresIn = "expires_in" + } } class Registry { @@ -16,6 +22,7 @@ class Registry { var namespace: String var token: String? = nil + var tokenExpiresAt: Date? = nil init(host: String, namespace: String) throws { var baseURLComponents = URLComponents() @@ -147,6 +154,12 @@ class Registry { } request.httpBody = body + // Invalidate token if it has expired + if let tokenExpiresAt = tokenExpiresAt, tokenExpiresAt >= Date() { + self.token = nil + self.tokenExpiresAt = nil + } + var (data, response) = try await authAwareRequest(request: request) if response.statusCode == 401 { @@ -199,13 +212,24 @@ class Registry { headers["Authorization"] = "Basic \(encodedCredentials!)" } - let (responseData, response) = try await rawRequest("GET", authenticateURL, headers: headers) + let (tokenResponseRaw, response) = try await rawRequest("GET", authenticateURL, headers: headers) if response.statusCode != 200 { throw RegistryError.AuthFailed(why: "received unexpected HTTP status code \(response.statusCode) " + "while retrieving an authentication token", details: String(decoding: responseData, as: UTF8.self)) } - token = try JSONDecoder().decode(TokenResponse.self, from: responseData).token + let tokenResponse = try JSONDecoder().decode(TokenResponse.self, from: tokenResponseRaw) + + token = tokenResponse.token + + // Tokens can expire and expire_in field is used to determine when: + // + // >The duration in seconds since the token was issued that it will remain valid. + // >When omitted, this defaults to 60 seconds. For compatibility with older clients, + // >a token should never be returned with less than 60 seconds to live. + // + // [1]: https://docs.docker.com/registry/spec/auth/token/#requesting-a-token + tokenExpiresAt = Date() + TimeInterval(tokenResponse.expiresIn ?? 60) } private func authAwareRequest(request: URLRequest) async throws -> (Data, HTTPURLResponse) {