Files
suixinkan_uikit/suixinkan/Features/ScenicQueue/Services/AliyunNLSTokenProvider.swift

136 lines
4.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// AliyunNLSTokenProvider.swift
// suixinkan
//
import CryptoKit
import Foundation
/// NLS token 访
struct AliyunNLSToken: Equatable {
let id: String
let expireTime: Int64?
}
/// NLS token 便 TTS
protocol AliyunNLSTokenProviding {
/// 使 STS NLS token
func createToken(credentials: AliyunOSSCredentials) async throws -> AliyunNLSToken
}
/// NLS token Provider OpenAPI POP CreateToken
final class AliyunNLSTokenProvider: AliyunNLSTokenProviding {
typealias DataLoader = (URLRequest) async throws -> Data
private let endpoint: URL
private let dateProvider: () -> Date
private let nonceProvider: () -> String
private let dataLoader: DataLoader
init(
endpoint: URL = URL(string: "https://nls-meta.cn-shanghai.aliyuncs.com/")!,
dateProvider: @escaping () -> Date = Date.init,
nonceProvider: @escaping () -> String = { UUID().uuidString },
dataLoader: @escaping DataLoader = { request in
let (data, response) = try await URLSession.shared.data(for: request)
if let http = response as? HTTPURLResponse, !(200...299).contains(http.statusCode) {
throw AliyunNLSTokenError.httpStatus(http.statusCode)
}
return data
}
) {
self.endpoint = endpoint
self.dateProvider = dateProvider
self.nonceProvider = nonceProvider
self.dataLoader = dataLoader
}
func createToken(credentials: AliyunOSSCredentials) async throws -> AliyunNLSToken {
var params = signedBaseParams(credentials: credentials)
params["Signature"] = Self.signature(params: params, accessKeySecret: credentials.accessKeySecret)
let query = Self.canonicalQuery(params)
guard let url = URL(string: endpoint.absoluteString + "?" + query) else {
throw AliyunNLSTokenError.invalidURL
}
let request = URLRequest(url: url)
let data = try await dataLoader(request)
let response = try JSONDecoder().decode(AliyunNLSCreateTokenResponse.self, from: data)
guard let id = response.token?.id?.trimmingCharacters(in: .whitespacesAndNewlines), !id.isEmpty else {
throw AliyunNLSTokenError.missingToken
}
return AliyunNLSToken(id: id, expireTime: response.token?.expireTime)
}
/// CreateToken
func signedBaseParams(credentials: AliyunOSSCredentials) -> [String: String] {
[
"AccessKeyId": credentials.accessKeyId,
"Action": "CreateToken",
"Format": "JSON",
"RegionId": "cn-shanghai",
"SignatureMethod": "HMAC-SHA1",
"SignatureNonce": nonceProvider(),
"SignatureVersion": "1.0",
"Timestamp": Self.timestampFormatter.string(from: dateProvider()),
"Version": "2019-02-28",
"SecurityToken": credentials.securityToken,
]
}
/// POP HMAC-SHA1
static func signature(params: [String: String], accessKeySecret: String) -> String {
let canonical = canonicalQuery(params)
let stringToSign = "GET&%2F&\(percentEncode(canonical))"
let key = SymmetricKey(data: Data("\(accessKeySecret)&".utf8))
let mac = HMAC<Insecure.SHA1>.authenticationCode(for: Data(stringToSign.utf8), using: key)
return Data(mac).base64EncodedString()
}
/// OpenAPI
static func canonicalQuery(_ params: [String: String]) -> String {
params
.sorted { $0.key < $1.key }
.map { "\(percentEncode($0.key))=\(percentEncode($0.value))" }
.joined(separator: "&")
}
/// POP RFC3986
static func percentEncode(_ value: String) -> String {
var allowed = CharacterSet.alphanumerics
allowed.insert(charactersIn: "-_.~")
return value.addingPercentEncoding(withAllowedCharacters: allowed) ?? value
}
private static let timestampFormatter: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime]
formatter.timeZone = TimeZone(secondsFromGMT: 0)
return formatter
}()
}
/// NLS token
enum AliyunNLSTokenError: Error, Equatable {
case invalidURL
case httpStatus(Int)
case missingToken
}
private struct AliyunNLSCreateTokenResponse: Decodable {
let token: TokenPayload?
enum CodingKeys: String, CodingKey {
case token = "Token"
}
struct TokenPayload: Decodable {
let id: String?
let expireTime: Int64?
enum CodingKeys: String, CodingKey {
case id = "Id"
case expireTime = "ExpireTime"
}
}
}