Use headers for all routes

This commit is contained in:
Christoph Hagen 2022-10-12 21:53:16 +02:00
parent 213bb1c179
commit 30abe56325
3 changed files with 245 additions and 146 deletions

View File

@ -10,6 +10,8 @@ const headerKeyPassword = "password";
const headerKeyToken = "token"; const headerKeyToken = "token";
const headerKeyName = "name"; const headerKeyName = "name";
const headerKeyMail = "email"; const headerKeyMail = "email";
const headerKeyVisibility = "visibility";
const headerKeyAction = "action";
function webSocketPath() { function webSocketPath() {
const prefix = (window.location.protocol === "https:") ? "wss://" : "ws://" const prefix = (window.location.protocol === "https:") ? "wss://" : "ws://"
@ -29,29 +31,50 @@ async function performRegisterPlayerRequest(name, password, email) {
} }
async function performDeletePlayerRequest(name, password) { async function performDeletePlayerRequest(name, password) {
return fetch(apiPath + "/player/delete/" + name, { method: 'POST', body: password }) return fetch(apiPath + "/player/delete", {
method: 'POST',
headers: {
[headerKeyName]: name,
[headerKeyPassword]: password,
}
})
.then(convertServerResponse) .then(convertServerResponse)
.then(function(value) {}) .then(function(value) {})
} }
async function performLoginPlayerRequest(name, password) { async function performLoginPlayerRequest(name, password) {
return fetch(apiPath + "/player/login/" + name, { method: 'POST', body: password }) return fetch(apiPath + "/player/login", {
method: 'POST',
headers: {
[headerKeyName]: name,
[headerKeyPassword]: password,
}
})
.then(convertServerResponse) .then(convertServerResponse)
} }
async function performLogoutRequest(token) { async function performLogoutRequest(token) {
return fetch(apiPath + "/player/logout", { method: 'POST', body: token }) return fetch(apiPath + "/player/logout", {
method: 'POST',
headers: { [headerKeyToken]: token },
})
.then(convertServerResponse) .then(convertServerResponse)
.then(function(value) {}) .then(function(value) {})
} }
async function resumeSessionRequest(token) { async function resumeSessionRequest(token) {
return fetch(apiPath + "/player/resume", { method: 'POST', body: token }) return fetch(apiPath + "/player/resume", {
method: 'POST',
headers: { [headerKeyToken]: token },
})
.then(convertServerResponse) .then(convertServerResponse)
} }
async function performGetCurrentTableRequest(token) { async function performGetCurrentTableRequest(token) {
return fetch(apiPath + "/player/table", { method: 'POST', body: token }) return fetch(apiPath + "/player/table", {
method: 'POST',
headers: [headerKeyToken]: token,
})
.then(convertServerResponse) .then(convertServerResponse)
.then(convertJsonResponse) .then(convertJsonResponse)
} }
@ -65,7 +88,7 @@ async function performRecoveryEmailRequest(name) {
} }
async function performResetPasswordRequest(token, password) { async function performResetPasswordRequest(token, password) {
return fetch(apiPath + "/player/reset", { return fetch(apiPath + "/player/password/new", {
method: 'POST', method: 'POST',
headers: { [headerKeyPassword] : password, [headerKeyToken] : token } headers: { [headerKeyPassword] : password, [headerKeyToken] : token }
}) })
@ -74,36 +97,68 @@ async function performResetPasswordRequest(token, password) {
async function performCreateTableRequest(token, name, visibility) { async function performCreateTableRequest(token, name, visibility) {
const vis = visibility ? "public" : "private"; const vis = visibility ? "public" : "private";
return fetch(apiPath + "/table/create/" + vis + "/" + name, { method: 'POST', body: token }) return fetch(apiPath + "/table/create", {
method: 'POST',
headers: {
[headerKeyVisibility]: vis,
[headerKeyToken]: token,
[headerKeyName]: name,
}
})
.then(convertServerResponse) .then(convertServerResponse)
.then(convertJsonResponse) .then(convertJsonResponse)
} }
async function performJoinTableRequest(tableId, token) { async function performJoinTableRequest(tableId, token) {
return fetch(apiPath + "/table/join/" + tableId, { method: 'POST', body: token }) return fetch(apiPath + "/table/join", {
method: 'POST',
headers: {
[headerKeyName]: tableId,
[headerKeyToken]: token,
}
})
.then(convertServerResponse) .then(convertServerResponse)
.then(convertJsonResponse) .then(convertJsonResponse)
} }
async function performGetPublicTablesRequest(token) { async function performGetPublicTablesRequest(token) {
return fetch(apiPath + "/tables/public", { method: 'POST', body: token }) return fetch(apiPath + "/tables/public", {
method: 'POST',
headers: { [headerKeyToken] : token }
})
.then(convertServerResponse) .then(convertServerResponse)
.then(convertJsonResponse) .then(convertJsonResponse)
} }
async function performLeaveTableRequest(token) { async function performLeaveTableRequest(token) {
return fetch(apiPath + "/table/leave", { method: 'POST', body: token }) return fetch(apiPath + "/table/leave", {
method: 'POST',
headers: { [headerKeyToken] : token },
})
.then(convertServerResponse) .then(convertServerResponse)
.then(function(value) {}) .then(function(value) {})
} }
async function performPlayerActionRequest(token, action) { async function performPlayerActionRequest(token, action) {
return fetch(apiPath + "/player/action/" + action, { method: 'POST', body: token }) return fetch(apiPath + "/player/action", {
method: 'POST',
headers: {
[headerKeyToken] : token,
[headerKeyAction] : action,
},
})
.then(convertServerResponse) .then(convertServerResponse)
.then(function(value) {})
} }
async function performPlayCardRequest(token, card) { async function performPlayCardRequest(token, card) {
return fetch(apiPath + "/player/card/" + card, { method: 'POST', body: token }) return fetch(apiPath + "/player/card", {
method: 'POST',
headers: {
[headerKeyToken] : token,
[headerKeyAction] : card,
},
})
.then(convertServerResponse) .then(convertServerResponse)
.then(function(value) {}) .then(function(value) {})
} }

View File

@ -6,6 +6,8 @@ enum HeaderKey: String {
case password case password
case email case email
case token case token
case visibility
case action
} }
extension Request { extension Request {

View File

@ -98,7 +98,7 @@ func requestPlayerPasswordReset(_ app: Application) {
- `424`: Password could not be hashed - `424`: Password could not be hashed
*/ */
func resetPlayerPasswordWithEmailToken(_ app: Application) { func resetPlayerPasswordWithEmailToken(_ app: Application) {
app.post("player", "reset") { req async throws -> HTTPResponseStatus in app.post("player", "password", "new") { req async throws -> HTTPResponseStatus in
let token = try req.header(.token) // 400 let token = try req.header(.token) // 400
let hash = try req.hashedPassword() // errors: 400, 424 let hash = try req.hashedPassword() // errors: 400, 424
try await server.updatePassword(password: hash, forResetToken: token, in: req.db) // 417 try await server.updatePassword(password: hash, forResetToken: token, in: req.db) // 417
@ -109,21 +109,20 @@ func resetPlayerPasswordWithEmailToken(_ app: Application) {
/** /**
Delete a player. Delete a player.
- Parameter name: The name of the player, included in the url **Headers**
- Parameter password: The password of the player, as a string in the request body - `name`: The name of the player
- `password`: The password of the player
Possible errors: **Possible errors**
- `400`: Missing name or password - `400`: Missing name or password
- `401`: The password or user name is invalid - `401`: The password or user name is invalid
- `424`: The password could not be hashed - `424`: The password could not be hashed
- Returns: Nothing
*/ */
func deletePlayer(_ app: Application) { func deletePlayer(_ app: Application) {
app.post("player", "delete", ":name") { request async throws -> HTTPResponseStatus in app.post("player", "delete") { request async throws -> HTTPResponseStatus in
guard let name = request.parameters.get("name"), let name = try request.header(.name) // 400
let password = request.body.string else { let password = try request.header(.password) // 400
return .badRequest // 400
}
let hash = try await server.passwordHashForExistingPlayer(named: name, in: request.db) let hash = try await server.passwordHashForExistingPlayer(named: name, in: request.db)
guard try request.password.verify(password, created: hash) else { guard try request.password.verify(password, created: hash) else {
return .unauthorized // 401 return .unauthorized // 401
@ -135,22 +134,24 @@ func deletePlayer(_ app: Application) {
/** /**
Log in as an existing player. Log in as an existing player.
- Parameter name: The name of the player, included in the url
- Parameter password: The password of the player, as a string in the request body
Possible errors: **Headers**
- `name`: The name of the player
- `password`: The password of the player
**Possible errors**
- `400`: Missing name or password - `400`: Missing name or password
- `401`: The password or user name is invalid - `401`: The password or user name is invalid
- `424`: The password could not be hashed - `424`: The password could not be hashed
- Returns: The session token for the user **Response**
- `body`: The session token for the user
*/ */
func loginPlayer(_ app: Application) { func loginPlayer(_ app: Application) {
app.post("player", "login", ":name") { request async throws -> String in app.post("player", "login") { request async throws -> String in
guard let name = request.parameters.get("name"), let name = try request.header(.name) // 400
let password = request.body.string else { let password = try request.header(.password) // 400
throw Abort(.badRequest) // 400
}
let hash = try await server.passwordHashForExistingPlayer(named: name, in: request.db) let hash = try await server.passwordHashForExistingPlayer(named: name, in: request.db)
guard try request.password.verify(password, created: hash) else { guard try request.password.verify(password, created: hash) else {
throw Abort(.unauthorized) // 401 throw Abort(.unauthorized) // 401
@ -159,19 +160,23 @@ func loginPlayer(_ app: Application) {
} }
} }
/** /**
Log in using a session token. Log in using a session token.
- Parameter token: The session token of the player, as a string in the request body
- Throws: **Headers**
- 400: Missing token - `token`: The session token of the player
- 401: The token is invalid
- Returns: The player name associated with the session token **Possible errors**
- `400`: Missing token
- `401`: The token is invalid
**Response**
- `body`: The player name associated with the session token
*/ */
func resumeSession(_ app: Application) { func resumeSession(_ app: Application) {
app.post("player", "resume") { req -> String in app.post("player", "resume") { request -> String in
guard let token = req.body.string else { let token = try request.header(.token)
throw Abort(.badRequest) // 400
}
guard let player = server.registeredPlayerExists(withSessionToken: token) else { guard let player = server.registeredPlayerExists(withSessionToken: token) else {
throw Abort(.unauthorized) // 401 throw Abort(.unauthorized) // 401
} }
@ -179,38 +184,43 @@ func resumeSession(_ app: Application) {
} }
} }
/** /**
Log out. Log out.
- Parameter name: The name of the player, included in the url
- Parameter token: The session token of the player, as a string in the request body **Headers**
- Throws: - `token`: The session token of the player
- 400: Missing token
- Returns: Nothing **Possible errors**
- `400`: Missing token
- Note: The request always succeeds when correctly formed, even for invalid and expired tokens - Note: The request always succeeds when correctly formed, even for invalid and expired tokens
*/ */
func logoutPlayer(_ app: Application) { func logoutPlayer(_ app: Application) {
app.post("player", "logout") { req -> String in app.post("player", "logout") { request -> HTTPResponseStatus in
guard let token = req.body.string else { let token = try request.header(.token)
throw Abort(.badRequest) // 400
}
server.endSession(forSessionToken: token) server.endSession(forSessionToken: token)
return "" return .ok
} }
} }
/** /**
Get the current table of the player, if one exists. Get the current table of the player, if one exists.
- Parameter token: The session token of the player, as a string in the request body
- Throws: **Headers**
- 400: Missing token - `token`: The session token of the player
- 401: Invalid token
- Returns: The table info, or an empty string **Possible errors**
- `400`: Missing token
- `401`: The token is invalid
**Response**
- `body`: The table info, or an empty string
*/ */
func getTableForPlayer(_ app: Application) { func getTableForPlayer(_ app: Application) {
app.post("player", "table") { req -> String in app.post("player", "table") { request -> String in
guard let token = req.body.string else { let token = try request.header(.token)
throw Abort(.badRequest) // 400
}
guard let player = server.registeredPlayerExists(withSessionToken: token) else { guard let player = server.registeredPlayerExists(withSessionToken: token) else {
throw Abort(.unauthorized) // 401 throw Abort(.unauthorized) // 401
} }
@ -221,9 +231,9 @@ func getTableForPlayer(_ app: Application) {
} }
} }
/** /**
Start a new websocket connection for the client to receive table updates from the server Start a new websocket connection for the client to receive table updates from the server
- Returns: Nothing
- Note: The first (and only) message from the client over the connection must be a valid session token. - Note: The first (and only) message from the client over the connection must be a valid session token.
*/ */
func openWebsocket(_ app: Application) { func openWebsocket(_ app: Application) {
@ -237,24 +247,29 @@ func openWebsocket(_ app: Application) {
} }
} }
// MARK: Tables // MARK: Tables
/** /**
Create a new table. Create a new table.
- Parameter visibility: Indicate a `"public"` or `"private"` table
- Parameter token: The session token of the player, as a string in the request body **Headers**
- Returns: The table id - `name`: The name of the table
- Throws: - `token`: The session token of the player
- 400: Missing token, table name or invalid visibility - `visibility`: The visibility of the table (`private` or `public`)
- 401: The session token is invalid
**Errors**
- `400`: Missing token, table name or invalid visibility
- `401`: The session token is invalid
**Response**
- `body`: The table id
*/ */
func createTable(_ app: Application) { func createTable(_ app: Application) {
app.post("table", "create", ":visibility", ":name") { request -> String in app.post("table", "create") { request -> String in
guard let visibility = request.parameters.get("visibility"), let tableName = try request.header(.name)
let tableName = request.parameters.get("name"), let token = try request.header(.token)
let token = request.body.string else { let visibility = try request.header(.visibility)
throw Abort(.badRequest) // 400
}
let isPublic: Bool let isPublic: Bool
if visibility == "private" { if visibility == "private" {
isPublic = false isPublic = false
@ -276,18 +291,19 @@ func createTable(_ app: Application) {
List the public tables. List the public tables.
**Headers** **Headers**
- `token`: The session token of the player, as a string in the request body - `token`: The session token of the player
**Possible errors** **Possible errors**
- `400`: Missing token - `400`: Missing token
- `401`: The session token is invalid - `401`: The session token is invalid
- Returns: A JSON object with a list of public tables (id, name, player list)
**Response**
- `body`: A JSON object with a list of public tables (id, name, player list)
*/ */
func getPublicTables(_ app: Application) { func getPublicTables(_ app: Application) {
app.post("tables", "public") { req -> String in app.post("tables", "public") { request -> String in
guard let token = req.body.string else { let token = try request.header(.token)
throw Abort(.badRequest) // 400
}
guard server.isValid(sessionToken: token) else { guard server.isValid(sessionToken: token) else {
throw Abort(.unauthorized) // 401 throw Abort(.unauthorized) // 401
} }
@ -296,24 +312,25 @@ func getPublicTables(_ app: Application) {
} }
} }
/** /**
Join a table. Join a table.
- Parameter table: The table id
- Parameter token: The session token of the player, as a string in the request body
- Throws:
- 400: Missing token
- 401: The session token is invalid
- 403: The player already sits at another table
- 410: The table id doesn't exist
- 417: The table is already full and can't be joined
- Returns: Nothing
*/
**Headers**
- `name`: The table id
- `token`: The session token of the player
**Possible errors**
- `400`: Missing token
- `401`: The session token is invalid
- `403`: The player already sits at another table
- `410`: The table id doesn't exist
- `417`: The table is already full and can't be joined
*/
func joinTable(_ app: Application) { func joinTable(_ app: Application) {
app.post("table", "join", ":table") { request -> String in app.post("table", "join") { request -> String in
guard let string = request.parameters.get("table"), let string = try request.header(.name)
let table = UUID(uuidString: string), let token = try request.header(.token)
let token = request.body.string else { guard let table = UUID(uuidString: string) else {
throw Abort(.badRequest) throw Abort(.badRequest)
} }
let result = try await server.join(tableId: table, playerToken: token, in: request.db) let result = try await server.join(tableId: table, playerToken: token, in: request.db)
@ -321,30 +338,42 @@ func joinTable(_ app: Application) {
} }
} }
/** /**
Leave the current table. Leave the current table.
- Parameter token: The session token of the player, as a string in the request body
- Throws: **Headers**
- 400: Missing token - `token`: The session token of the player
- 401: The session token is invalid
- Returns: Nothing **Possible errors**
- `400`: Missing token
- `401`: The session token is invalid
*/ */
func leaveTable(_ app: Application) { func leaveTable(_ app: Application) {
app.post("table", "leave") { request -> HTTPResponseStatus in app.post("table", "leave") { request -> HTTPResponseStatus in
guard let token = request.body.string else { let token = try request.header(.token)
throw Abort(.badRequest)
}
try await server.leaveTable(playerToken: token, in: request.db) try await server.leaveTable(playerToken: token, in: request.db)
return .ok return .ok
} }
} }
/**
Perform an action, either a game selection or a player action.
**Headers**
- `token`: The session token of the player
- `action`: The action to perform
**Possible errors**
- `400`: Missing token or action
- `401`: The session token is invalid
- `412`: The action is not allowed
*/
func performActionForPlayer(_ app: Application) { func performActionForPlayer(_ app: Application) {
app.post("player", "action", ":action") { req -> String in app.post("player", "action") { request -> HTTPResponseStatus in
guard let token = req.body.string, let token = try request.header(.token)
let actionString = req.parameters.get("action") else { let actionString = try request.header(.action)
throw Abort(.badRequest)
}
let result: PlayerActionResult let result: PlayerActionResult
if let action = PlayerAction(rawValue: actionString) { if let action = PlayerAction(rawValue: actionString) {
result = server.performAction(playerToken: token, action: action) result = server.performAction(playerToken: token, action: action)
@ -355,41 +384,54 @@ func performActionForPlayer(_ app: Application) {
} }
switch result { switch result {
case .success: case .success:
return "" return .ok
case .invalidToken: case .invalidToken:
throw Abort(.unauthorized) // 401 return .unauthorized // 401
case .noTableJoined: case .noTableJoined:
throw Abort(.preconditionFailed) // 412 return .preconditionFailed // 412
case .tableNotFull: case .tableNotFull:
throw Abort(.preconditionFailed) // 412 return .preconditionFailed // 412
case .tableStateInvalid: case .tableStateInvalid:
throw Abort(.preconditionFailed) // 412 return .preconditionFailed // 412
case .invalidCard: case .invalidCard:
throw Abort(.preconditionFailed) // 412 return .preconditionFailed // 412
} }
} }
} }
/**
Play a card as the active player.
**Headers**
- `token`: The session token of the player
- `action`: The id of the card to play
**Possible errors**
- `400`: Missing token or card id
- `401`: The session token is invalid
- `412`: The action is not allowed
*/
func playCard(_ app: Application) { func playCard(_ app: Application) {
app.post("player", "card", ":card") { req -> String in app.post("player", "card") { request -> HTTPResponseStatus in
guard let token = req.body.string, let token = try request.header(.token)
let cardId = req.parameters.get("card"), let cardId = try request.header(.action)
let card = Card(id: cardId) else { guard let card = Card(id: cardId) else {
throw Abort(.badRequest) throw Abort(.badRequest)
} }
switch server.play(card: card, playerToken: token) { switch server.play(card: card, playerToken: token) {
case .success: case .success:
return "" return .ok
case .invalidToken: case .invalidToken:
throw Abort(.unauthorized) // 401 return .unauthorized // 401
case .noTableJoined: case .noTableJoined:
throw Abort(.preconditionFailed) // 412 return .preconditionFailed // 412
case .tableStateInvalid: case .tableStateInvalid:
throw Abort(.preconditionFailed) // 412 return .preconditionFailed // 412
case .invalidCard: case .invalidCard:
throw Abort(.preconditionFailed) // 412 return .preconditionFailed // 412
case .tableNotFull: case .tableNotFull:
throw Abort(.preconditionFailed) // 412 return .preconditionFailed // 412
} }
} }
} }