Update device authentication
This commit is contained in:
parent
6117ae8305
commit
f4864127f8
@ -3,18 +3,6 @@ import WebSocketKit
|
|||||||
import Vapor
|
import Vapor
|
||||||
import Clairvoyant
|
import Clairvoyant
|
||||||
|
|
||||||
enum DeviceState: UInt8 {
|
|
||||||
|
|
||||||
case disconnected = 0
|
|
||||||
case connected = 1
|
|
||||||
case authenticated = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
extension DeviceState: MetricValue {
|
|
||||||
|
|
||||||
static let valueType: MetricType = .customType(named: "DeviceState")
|
|
||||||
}
|
|
||||||
|
|
||||||
final class DeviceManager {
|
final class DeviceManager {
|
||||||
|
|
||||||
/// The connection to the device
|
/// The connection to the device
|
||||||
@ -26,28 +14,17 @@ final class DeviceManager {
|
|||||||
/// The authentication token of the remote
|
/// The authentication token of the remote
|
||||||
private let remoteKey: Data
|
private let remoteKey: Data
|
||||||
|
|
||||||
/// Indicate that the socket is fully initialized with an authorized device
|
|
||||||
private var deviceIsAuthenticated = false
|
|
||||||
|
|
||||||
private let deviceTimeout: Int64
|
private let deviceTimeout: Int64
|
||||||
|
|
||||||
private let deviceStateMetric: Metric<DeviceState>
|
private let deviceConnectedMetric: Metric<Bool>
|
||||||
|
|
||||||
private let messagesToDeviceMetric: Metric<Int>
|
private let messagesToDeviceMetric: Metric<Int>
|
||||||
|
|
||||||
var deviceState: DeviceState {
|
|
||||||
guard let connection, !connection.isClosed else {
|
|
||||||
return .disconnected
|
|
||||||
}
|
|
||||||
guard deviceIsAuthenticated else {
|
|
||||||
return .connected
|
|
||||||
}
|
|
||||||
return .authenticated
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Indicator for device availability
|
|
||||||
var deviceIsConnected: Bool {
|
var deviceIsConnected: Bool {
|
||||||
deviceIsAuthenticated && !(connection?.isClosed ?? true)
|
guard let connection, !connection.isClosed else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A promise to finish the request once the device responds or times out
|
/// A promise to finish the request once the device responds or times out
|
||||||
@ -57,10 +34,10 @@ final class DeviceManager {
|
|||||||
self.deviceKey = deviceKey
|
self.deviceKey = deviceKey
|
||||||
self.remoteKey = remoteKey
|
self.remoteKey = remoteKey
|
||||||
self.deviceTimeout = deviceTimeout
|
self.deviceTimeout = deviceTimeout
|
||||||
self.deviceStateMetric = .init(
|
self.deviceConnectedMetric = .init(
|
||||||
"sesame.device",
|
"sesame.connected",
|
||||||
name: "Device status",
|
name: "Device connection",
|
||||||
description: "Shows if the device is connected and authenticated via WebSocket")
|
description: "Shows if the device is connected via WebSocket")
|
||||||
self.messagesToDeviceMetric = .init(
|
self.messagesToDeviceMetric = .init(
|
||||||
"sesame.messages",
|
"sesame.messages",
|
||||||
name: "Forwarded Messages",
|
name: "Forwarded Messages",
|
||||||
@ -68,7 +45,7 @@ final class DeviceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateDeviceConnectionMetric() async {
|
func updateDeviceConnectionMetric() async {
|
||||||
_ = try? await deviceStateMetric.update(deviceState)
|
_ = try? await deviceConnectedMetric.update(deviceIsConnected)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateMessageCountMetric() async {
|
private func updateMessageCountMetric() async {
|
||||||
@ -78,10 +55,6 @@ final class DeviceManager {
|
|||||||
|
|
||||||
// MARK: API
|
// MARK: API
|
||||||
|
|
||||||
private var deviceStatus: String {
|
|
||||||
"\(deviceState.rawValue)"
|
|
||||||
}
|
|
||||||
|
|
||||||
func sendMessageToDevice(_ message: Data, on eventLoop: EventLoop) async throws -> Data {
|
func sendMessageToDevice(_ message: Data, on eventLoop: EventLoop) async throws -> Data {
|
||||||
guard let socket = connection, !socket.isClosed else {
|
guard let socket = connection, !socket.isClosed else {
|
||||||
connection = nil
|
connection = nil
|
||||||
@ -120,21 +93,6 @@ final class DeviceManager {
|
|||||||
requestInProgress = nil
|
requestInProgress = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func authenticateDevice(hash: String) async {
|
|
||||||
guard let key = Data(fromHexEncodedString: hash),
|
|
||||||
SHA256.hash(data: key) == self.deviceKey else {
|
|
||||||
log("Invalid device key")
|
|
||||||
await removeDeviceConnection()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let connection, !connection.isClosed else {
|
|
||||||
await updateDeviceConnectionMetric()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
deviceIsAuthenticated = true
|
|
||||||
await updateDeviceConnectionMetric()
|
|
||||||
}
|
|
||||||
|
|
||||||
func authenticateRemote(_ token: Data) -> Bool {
|
func authenticateRemote(_ token: Data) -> Bool {
|
||||||
let hash = SHA256.hash(data: token)
|
let hash = SHA256.hash(data: token)
|
||||||
return hash == remoteKey
|
return hash == remoteKey
|
||||||
@ -150,32 +108,34 @@ final class DeviceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func didCloseDeviceSocket() {
|
func didCloseDeviceSocket() {
|
||||||
deviceIsAuthenticated = false
|
|
||||||
connection = nil
|
connection = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeDeviceConnection() async {
|
func removeDeviceConnection() async {
|
||||||
try? await connection?.close()
|
try? await connection?.close()
|
||||||
connection = nil
|
connection = nil
|
||||||
deviceIsAuthenticated = false
|
|
||||||
await updateDeviceConnectionMetric()
|
await updateDeviceConnectionMetric()
|
||||||
}
|
}
|
||||||
|
|
||||||
func createNewDeviceConnection(_ socket: WebSocket) async {
|
func createNewDeviceConnection(socket: WebSocket, auth: String) async {
|
||||||
|
guard let key = Data(fromHexEncodedString: auth),
|
||||||
|
SHA256.hash(data: key) == self.deviceKey else {
|
||||||
|
log("Invalid device key")
|
||||||
|
return
|
||||||
|
}
|
||||||
await removeDeviceConnection()
|
await removeDeviceConnection()
|
||||||
|
|
||||||
|
connection = socket
|
||||||
socket.eventLoop.execute {
|
socket.eventLoop.execute {
|
||||||
|
socket.pingInterval = .seconds(10)
|
||||||
|
|
||||||
socket.onBinary { [weak self] _, data in
|
socket.onBinary { [weak self] _, data in
|
||||||
self?.processDeviceResponse(data)
|
self?.processDeviceResponse(data)
|
||||||
}
|
}
|
||||||
socket.onText { [weak self] _, text async in
|
socket.onClose.whenComplete { [weak self] _ in
|
||||||
await self?.authenticateDevice(hash: text)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = socket.onClose.always { [weak self] _ in
|
|
||||||
self?.didCloseDeviceSocket()
|
self?.didCloseDeviceSocket()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
connection = socket
|
|
||||||
await updateDeviceConnectionMetric()
|
await updateDeviceConnectionMetric()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,11 @@ func routes(_ app: Application) throws {
|
|||||||
- Returns: Nothing
|
- Returns: Nothing
|
||||||
- Note: The first message from the device over the connection must be a valid auth token.
|
- Note: The first message from the device over the connection must be a valid auth token.
|
||||||
*/
|
*/
|
||||||
app.webSocket(RouteAPI.socket.path) { req, socket async in
|
app.webSocket(RouteAPI.socket.path) { request, socket async in
|
||||||
await deviceManager.createNewDeviceConnection(socket)
|
guard let authToken = request.headers.first(name: "Authorization") else {
|
||||||
|
try? await socket.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await deviceManager.createNewDeviceConnection(socket: socket, auth: authToken)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,8 @@ var env = Environment.production //.detect()
|
|||||||
try LoggingSystem.bootstrap(from: &env)
|
try LoggingSystem.bootstrap(from: &env)
|
||||||
let app = Application(env)
|
let app = Application(env)
|
||||||
defer {
|
defer {
|
||||||
app.shutdown()
|
|
||||||
shutdown()
|
shutdown()
|
||||||
|
app.shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
try configure(app)
|
try configure(app)
|
||||||
|
Loading…
Reference in New Issue
Block a user