Compare commits

...

2 Commits

42 changed files with 159 additions and 99 deletions

View File

@@ -5,7 +5,7 @@ info:
http:
method: POST
url: "{{BASE_URL}}/api/auth/login"
url: "{{BASE_URL}}/{{SECTOR}}/login"
headers:
- name: Content-Type
value: application/json

View File

@@ -5,7 +5,7 @@ info:
http:
method: GET
url: "{{BASE_URL}}/api/auth/token"
url: "{{BASE_URL}}/{{SECTOR}}/token"
headers:
- name: Cookie
value: "{{REFRESH_COOKIE}}"

View File

@@ -2,3 +2,8 @@ info:
name: Auth
type: folder
seq: 1
request:
variables:
- name: SECTOR
value: auth

View File

@@ -5,7 +5,7 @@ info:
http:
method: POST
url: "{{BASE_URL}}/api/collection/{{CollectionID}}/add"
url: "{{BASE_URL}}/{{SECTOR}}/{{CollectionID}}/add"
body:
type: json
data: |-

View File

@@ -5,7 +5,7 @@ info:
http:
method: POST
url: "{{BASE_URL}}/api/collection"
url: "{{BASE_URL}}/{{SECTOR}}"
body:
type: json
data: |-

View File

@@ -5,7 +5,7 @@ info:
http:
method: DELETE
url: "{{BASE_URL}}/api/collection/{{CollectionID}}"
url: "{{BASE_URL}}/{{SECTOR}}/{{CollectionID}}"
auth: inherit
runtime:

View File

@@ -5,11 +5,7 @@ info:
http:
method: GET
url: "{{BASE_URL}}/api/collection/{{CollectionID}}"
params:
- name: ""
value: ""
type: query
url: "{{BASE_URL}}/{{SECTOR}}/{{CollectionID}}"
auth: inherit
runtime:

View File

@@ -5,15 +5,15 @@ info:
http:
method: GET
url: "{{BASE_URL}}/api/collection/list/{{PageSize}}/{{Page}}"
url: "{{BASE_URL}}/{{SECTOR}}/list/{{PageSize}}/{{Page}}"
auth: inherit
runtime:
variables:
- name: PageSize
value: "1"
value: "5"
- name: Page
value: "0"
value: "1"
settings:
encodeUrl: true

View File

@@ -5,7 +5,7 @@ info:
http:
method: POST
url: "{{BASE_URL}}/api/collection/{{CollectionID}}/remove"
url: "{{BASE_URL}}/{{SECTOR}}/{{CollectionID}}/remove"
body:
type: json
data: |-

View File

@@ -5,7 +5,7 @@ info:
http:
method: PATCH
url: "{{BASE_URL}}/api/collection/{{CollectionID}}"
url: "{{BASE_URL}}/{{SECTOR}}/{{CollectionID}}"
body:
type: json
data: |-

View File

@@ -1,7 +1,10 @@
info:
name: Collections
type: folder
seq: 6
seq: 1
request:
auth: inherit
variables:
- name: SECTOR
value: collections

View File

@@ -5,7 +5,7 @@ info:
http:
method: POST
url: "{{BASE_URL}}/api/game"
url: "{{BASE_URL}}/{{SECTOR}}"
body:
type: json
data: |-

View File

@@ -5,7 +5,7 @@ info:
http:
method: DELETE
url: "{{BASE_URL}}/api/game/{{GameID}}"
url: "{{BASE_URL}}/{{SECTOR}}/{{GameID}}"
auth: inherit
runtime:

View File

@@ -5,7 +5,7 @@ info:
http:
method: GET
url: "{{BASE_URL}}/api/game/{{GameID}}"
url: "{{BASE_URL}}/{{SECTOR}}/{{GameID}}"
auth: inherit
runtime:

View File

@@ -5,7 +5,7 @@ info:
http:
method: GET
url: "{{BASE_URL}}/api/game/search/{{Query}}/{{PageSize}}/{{Page}}"
url: "{{BASE_URL}}/{{SECTOR}}/search/{{Query}}/{{PageSize}}/{{Page}}"
auth: inherit
runtime:
@@ -13,9 +13,9 @@ runtime:
- name: Query
value: test
- name: PageSize
value: "2"
value: "5"
- name: Page
value: "2"
value: "1"
settings:
encodeUrl: true

View File

@@ -5,7 +5,7 @@ info:
http:
method: PATCH
url: "{{BASE_URL}}/api/game/{{GameID}}"
url: "{{BASE_URL}}/{{SECTOR}}/{{GameID}}"
body:
type: json
data: |-

View File

@@ -1,7 +1,10 @@
info:
name: Game
type: folder
seq: 4
seq: 1
request:
auth: inherit
variables:
- name: SECTOR
value: games

View File

@@ -5,7 +5,7 @@ info:
http:
method: POST
url: "{{BASE_URL}}/api/invite/accept"
url: "{{BASE_URL}}/{{SECTOR}}/accept"
body:
type: json
data: |-

View File

@@ -5,7 +5,7 @@ info:
http:
method: POST
url: "{{BASE_URL}}/api/invite"
url: "{{BASE_URL}}/{{SECTOR}}"
body:
type: json
data: |-

View File

@@ -5,3 +5,6 @@ info:
request:
auth: inherit
variables:
- name: SECTOR
value: invites

View File

@@ -5,7 +5,7 @@ info:
http:
method: POST
url: "{{BASE_URL}}/api/player"
url: "{{BASE_URL}}/{{SECTOR}}"
body:
type: json
data: |-

View File

@@ -5,7 +5,7 @@ info:
http:
method: DELETE
url: "{{BASE_URL}}/api/player/{{PlayerID}}"
url: "{{BASE_URL}}/{{SECTOR}}/{{PlayerID}}"
auth: inherit
runtime:

View File

@@ -5,7 +5,7 @@ info:
http:
method: GET
url: "{{BASE_URL}}/api/player/{{PlayerID}}"
url: "{{BASE_URL}}/{{SECTOR}}/{{PlayerID}}"
auth: inherit
runtime:

View File

@@ -5,9 +5,16 @@ info:
http:
method: GET
url: "{{BASE_URL}}/api/player/list"
url: "{{BASE_URL}}/{{SECTOR}}/list/{{PageSize}}/{{Page}}"
auth: inherit
runtime:
variables:
- name: PageSize
value: "100"
- name: Page
value: "1"
settings:
encodeUrl: true
timeout: 0

View File

@@ -5,7 +5,7 @@ info:
http:
method: PATCH
url: "{{BASE_URL}}/api/player/{{PlayerID}}"
url: "{{BASE_URL}}/{{SECTOR}}/{{PlayerID}}"
body:
type: json
data: |-

View File

@@ -1,7 +1,10 @@
info:
name: Players
type: folder
seq: 2
seq: 1
request:
auth: inherit
variables:
- name: SECTOR
value: players

View File

@@ -5,7 +5,7 @@ info:
http:
method: POST
url: "{{BASE_URL}}/api/user"
url: "{{BASE_URL}}/{{SECTOR}}"
body:
type: json
data: |-

View File

@@ -5,7 +5,7 @@ info:
http:
method: DELETE
url: "{{BASE_URL}}/api/user/{{UserID}}"
url: "{{BASE_URL}}/{{SECTOR}}/{{UserID}}"
auth: inherit
runtime:

View File

@@ -5,7 +5,7 @@ info:
http:
method: GET
url: "{{BASE_URL}}/api/user/{{UserID}}"
url: "{{BASE_URL}}/{{SECTOR}}/{{UserID}}"
auth: inherit
runtime:

View File

@@ -5,7 +5,7 @@ info:
http:
method: PATCH
url: "{{BASE_URL}}/api/user/{{UserID}}"
url: "{{BASE_URL}}/{{SECTOR}}/{{UserID}}"
body:
type: json
data: |-

View File

@@ -1,7 +1,10 @@
info:
name: User
type: folder
seq: 3
seq: 1
request:
auth: inherit
variables:
- name: SECTOR
value: users

View File

@@ -5,4 +5,4 @@ variables:
- name: REFRESH_COOKIE
value: ""
- name: BASE_URL
value: http://localhost:3000
value: http://localhost:3000/api

View File

@@ -23,7 +23,7 @@ async function login(request: UnwrappedRequest<LoginRequest>): Promise<Response>
u: verify.userId.raw,
r: verify.refreshCount,
},
process.env.JWT_SECRET_KEY as string,
process.env.JWT_REFRESH_KEY as string,
{ expiresIn: `${tokenLifeSpanInDays * 24}h` },
);
const cookies = request?.request?.cookies;
@@ -52,7 +52,7 @@ async function token(request: UnwrappedRequest): Promise<Response> {
const refreshToken: {
u: string;
r: string;
} = jwt.verify(refreshCookie, process.env.JWT_SECRET_KEY as string) as { u: string; r: string };
} = jwt.verify(refreshCookie, process.env.JWT_REFRESH_KEY as string) as { u: string; r: string };
if (!(await orm.users.verifyRefreshCount(UserId.fromID(refreshToken.u), refreshToken.r))) {
const response = new UnauthorizedResponse('Invalid refresh token');

View File

@@ -5,21 +5,24 @@ import players from './routes/players';
import games from './routes/games';
import invites from './routes/invites';
import collections from './routes/collections';
import { buildRoute } from './utilities/routeBuilder';
const server = Bun.serve({
routes: {
...auth,
...users,
...players,
...games,
...invites,
...collections,
'/test': {
routes: buildRoute({
[process.env.API_ROOT_PATH ?? '']:{
auth,
users,
players,
games,
invites,
collections,
},
'test': {
GET: () => {
return new OkResponse();
},
},
},
}) as any,
// (optional) fallback for unmatched routes:
fetch(): Response {

View File

@@ -1,6 +1,6 @@
import { sql } from 'bun';
import { ClaimDefinition } from '../utilities/claimDefinitions';
import { SecureId, UserId } from '../utilities/secureIds';
import { UserId } from '../utilities/secureIds';
export class Claims extends ClaimDefinition {
userId?: UserId;

View File

@@ -1,22 +1,20 @@
import { guard, unwrapMethod } from '../utilities/guard';
import auth from '../endpoints/auth';
import { OkResponse } from '../utilities/responseHelper';
import { Claims } from '../orm/claims';
export default {
'/api/auth/login': {
login: {
POST: unwrapMethod(auth.login),
},
'/api/auth/token': {
token: {
GET: unwrapMethod(auth.token),
},
'/api/auth/logout': {
logout: {
POST: unwrapMethod(auth.logout),
},
'/api/auth/changePassword/:id': {
PATCH: guard(auth.changePassword, [Claims.ADMIN, Claims.USERS.SELF.UPDATE]),
},
'/api/auth/test': {
GET: () => new OkResponse(),
changePassword: {
':id': {
PATCH: guard(auth.changePassword, [Claims.ADMIN, Claims.USERS.SELF.UPDATE]),
},
},
};

View File

@@ -3,29 +3,28 @@ import { Claims } from '../orm/claims';
import collections from '../endpoints/collections';
export default {
'/api/collection': {
POST: guard(collections.create, [Claims.ADMIN, Claims.COLLECTIONS.CREATE]),
},
'/api/collection/:id': {
'POST': guard(collections.create, [Claims.ADMIN, Claims.COLLECTIONS.CREATE]),
':id': {
GET: guard(collections.get, [Claims.ADMIN, Claims.COLLECTIONS.UNOWNED.READ, Claims.COLLECTIONS.OWNED.READ]),
// PATCH: guard(collections.update, [Claims.ADMIN, Claims.PLAYERS.OTHER.UPDATE, Claims.PLAYERS.SELF.UPDATE]),
// DELETE: guard(collections.drop, [Claims.ADMIN, Claims.PLAYERS.OTHER.DELETE, Claims.PLAYERS.SELF.DELETE]),
PATCH: guard(collections.update, [Claims.ADMIN, Claims.PLAYERS.OTHER.UPDATE, Claims.PLAYERS.SELF.UPDATE]),
DELETE: guard(collections.drop, [Claims.ADMIN, Claims.PLAYERS.OTHER.DELETE, Claims.PLAYERS.SELF.DELETE]),
add: {
POST: guard(collections.addGame, [
Claims.ADMIN,
Claims.COLLECTIONS.UNOWNED.GAME.ADD,
Claims.COLLECTIONS.OWNED.GAME.ADD,
]),
},
remove: {
POST: guard(collections.removeGame, [
Claims.ADMIN,
Claims.COLLECTIONS.UNOWNED.GAME.REMOVE,
Claims.COLLECTIONS.OWNED.GAME.REMOVE,
]),
},
},
'/api/collection/:id/add': {
POST: guard(collections.addGame, [
Claims.ADMIN,
Claims.COLLECTIONS.UNOWNED.GAME.ADD,
Claims.COLLECTIONS.OWNED.GAME.ADD,
]),
},
'/api/collection/:id/remove': {
POST: guard(collections.removeGame, [
Claims.ADMIN,
Claims.COLLECTIONS.UNOWNED.GAME.REMOVE,
Claims.COLLECTIONS.OWNED.GAME.REMOVE,
]),
},
'/api/collection/list/:pageSize/:page': {
'list': {
variants: [':pageSize/:page', ':page'],
GET: guard(collections.list, [Claims.ADMIN, Claims.COLLECTIONS.OWNED.LIST]),
},
};

View File

@@ -3,15 +3,16 @@ import { Claims } from '../orm/claims';
import games from '../endpoints/games';
export default {
'/api/game': {
POST: guard(games.create, [Claims.ADMIN, Claims.GAMES.CREATE]),
},
'/api/game/:id': {
'POST': guard(games.create, [Claims.ADMIN, Claims.GAMES.CREATE]),
':id': {
GET: guard(games.get, [Claims.ADMIN, Claims.GAMES.READ]),
PATCH: guard(games.update, [Claims.ADMIN, Claims.GAMES.UPDATE]),
DELETE: guard(games.drop, [Claims.ADMIN, Claims.GAMES.DELETE]),
},
'/api/game/search/:query/:pageSize/:page': {
GET: guard(games.query, [Claims.ADMIN, Claims.GAMES.READ]),
'search': {
':query': {
variants: [':pageSize/:page', ':page'],
GET: guard(games.query, [Claims.ADMIN, Claims.GAMES.READ]),
},
},
};

View File

@@ -3,10 +3,8 @@ import { Claims } from '../orm/claims';
import invite from '../endpoints/invites';
export default {
'/api/invite': {
POST: guard(invite.create, [Claims.ADMIN, Claims.USERS.INVITE]),
},
'/api/invite/accept': {
POST: guard(invite.create, [Claims.ADMIN, Claims.USERS.INVITE]),
accept: {
POST: unwrapMethod(invite.accept),
},
};

View File

@@ -3,15 +3,14 @@ import { Claims } from '../orm/claims';
import player from '../endpoints/players';
export default {
'/api/player': {
POST: guard(player.create, [Claims.ADMIN, Claims.PLAYERS.CREATE]),
},
'/api/player/:id': {
'POST': guard(player.create, [Claims.ADMIN, Claims.PLAYERS.CREATE]),
':id': {
GET: guard(player.get, [Claims.ADMIN, Claims.PLAYERS.OTHER.READ, Claims.PLAYERS.SELF.READ]),
PATCH: guard(player.update, [Claims.ADMIN, Claims.PLAYERS.OTHER.UPDATE, Claims.PLAYERS.SELF.UPDATE]),
DELETE: guard(player.drop, [Claims.ADMIN, Claims.PLAYERS.OTHER.DELETE, Claims.PLAYERS.SELF.DELETE]),
},
'/api/player/list/:pageSize/:page': {
'list': {
variants: [':pageSize/:page', ':page'],
GET: guard(player.list, [Claims.ADMIN, Claims.PLAYERS.OTHER.READ]),
},
};

View File

@@ -3,10 +3,8 @@ import user from '../endpoints/users';
import { Claims } from '../orm/claims';
export default {
'/api/user': {
POST: guard(user.create, [Claims.ADMIN, Claims.USERS.CREATE]),
},
'/api/user/:id': {
'POST': guard(user.create, [Claims.ADMIN, Claims.USERS.CREATE]),
':id': {
GET: guard(user.get, [Claims.ADMIN, Claims.USERS.OTHER.READ, Claims.USERS.SELF.READ]),
PATCH: guard(user.update, [Claims.ADMIN, Claims.USERS.OTHER.UPDATE, Claims.USERS.SELF.UPDATE]),
DELETE: guard(user.drop, [Claims.ADMIN, Claims.USERS.OTHER.UPDATE, Claims.USERS.SELF.UPDATE]),

View File

@@ -0,0 +1,41 @@
export function buildRoute(
route: any,
currentPath: string = '',
): {
[x: string]: {
POST?: Function;
GET?: Function;
PUT?: Function;
DELETE?: Function;
};
} {
let returnValue: { [x: string]: any } = {};
const keys = Object.keys(route);
for (let i in keys) {
const key = keys[i];
if (key === 'POST' || key === 'GET' || key === 'PUT' || key === 'DELETE' || key === 'variants') {
continue;
}
returnValue = {
...returnValue,
...buildRoute(route[key], `${currentPath}/${key}`),
};
}
if (route.variants || route.POST || route.GET || route.PUT || route.DELETE) {
const variants: string[] = route.variants ?? [];
const endpointDefinition = {
POST: route.POST,
GET: route.GET,
PUT: route.PUT,
DELETE: route.DELETE,
};
returnValue[currentPath] = endpointDefinition;
for (let key in variants) {
returnValue[`${currentPath}/${variants[key]}`] = endpointDefinition;
}
}
return returnValue;
}