Compare commits
3 Commits
f81220f837
...
59d2819750
| Author | SHA1 | Date | |
|---|---|---|---|
| 59d2819750 | |||
| c276ee4e17 | |||
| 8b9615c14b |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -3,6 +3,9 @@ node_modules/
|
||||
bun.lock
|
||||
package-lock.json
|
||||
.dockerignore
|
||||
bgapp
|
||||
./bgapp/
|
||||
.env.dev
|
||||
.env.test
|
||||
.env*
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
10
.idea_old/.gitignore
vendored
Normal file
10
.idea_old/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Ignored default folder with query files
|
||||
/queries/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
48
.idea_old/codeStyles/Project.xml
Normal file
48
.idea_old/codeStyles/Project.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<HTMLCodeStyleSettings>
|
||||
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||
</HTMLCodeStyleSettings>
|
||||
<JSCodeStyleSettings version="0">
|
||||
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||
<option name="USE_DOUBLE_QUOTES" value="false" />
|
||||
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
|
||||
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||
</JSCodeStyleSettings>
|
||||
<TypeScriptCodeStyleSettings version="0">
|
||||
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||
<option name="USE_DOUBLE_QUOTES" value="false" />
|
||||
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
|
||||
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||
</TypeScriptCodeStyleSettings>
|
||||
<VueCodeStyleSettings>
|
||||
<option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" />
|
||||
<option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" />
|
||||
</VueCodeStyleSettings>
|
||||
<codeStyleSettings language="HTML">
|
||||
<option name="SOFT_MARGINS" value="120" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="JavaScript">
|
||||
<option name="SOFT_MARGINS" value="120" />
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="TypeScript">
|
||||
<option name="SOFT_MARGINS" value="120" />
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="Vue">
|
||||
<option name="SOFT_MARGINS" value="120" />
|
||||
<indentOptions>
|
||||
<option name="INDENT_SIZE" value="4" />
|
||||
<option name="TAB_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
||||
5
.idea_old/codeStyles/codeStyleConfig.xml
Normal file
5
.idea_old/codeStyles/codeStyleConfig.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
||||
6
.idea_old/vcs.xml
Normal file
6
.idea_old/vcs.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
21
API Tests/BGApp/Collections/Create.yml
Normal file
21
API Tests/BGApp/Collections/Create.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
info:
|
||||
name: Create
|
||||
type: http
|
||||
seq: 1
|
||||
|
||||
http:
|
||||
method: POST
|
||||
url: http://localhost:3000/api/collection
|
||||
body:
|
||||
type: json
|
||||
data: |-
|
||||
{
|
||||
"name": "Invited player2"
|
||||
}
|
||||
auth: inherit
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
19
API Tests/BGApp/Collections/Delete.yml
Normal file
19
API Tests/BGApp/Collections/Delete.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
info:
|
||||
name: Delete
|
||||
type: http
|
||||
seq: 4
|
||||
|
||||
http:
|
||||
method: DELETE
|
||||
url: http://localhost:3000/api/collection/:id
|
||||
params:
|
||||
- name: id
|
||||
value: bmOe
|
||||
type: path
|
||||
auth: inherit
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
19
API Tests/BGApp/Collections/Get.yml
Normal file
19
API Tests/BGApp/Collections/Get.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
info:
|
||||
name: Get
|
||||
type: http
|
||||
seq: 2
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: http://localhost:3000/api/collection/:id
|
||||
params:
|
||||
- name: id
|
||||
value: ejRe
|
||||
type: path
|
||||
auth: inherit
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
15
API Tests/BGApp/Collections/List.yml
Normal file
15
API Tests/BGApp/Collections/List.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
info:
|
||||
name: List
|
||||
type: http
|
||||
seq: 5
|
||||
|
||||
http:
|
||||
method: GET
|
||||
url: http://localhost:3000/api/collection/list
|
||||
auth: inherit
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
27
API Tests/BGApp/Collections/Update.yml
Normal file
27
API Tests/BGApp/Collections/Update.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
info:
|
||||
name: Update
|
||||
type: http
|
||||
seq: 3
|
||||
|
||||
http:
|
||||
method: PATCH
|
||||
url: http://localhost:3000/api/collection/:id
|
||||
params:
|
||||
- name: id
|
||||
value: bmOe
|
||||
type: path
|
||||
body:
|
||||
type: json
|
||||
data: |-
|
||||
{
|
||||
"name": "Test Player",
|
||||
"isRatingLocked": true,
|
||||
"canBeMultiple": false
|
||||
}
|
||||
auth: inherit
|
||||
|
||||
settings:
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
followRedirects: true
|
||||
maxRedirects: 5
|
||||
7
API Tests/BGApp/Collections/folder.yml
Normal file
7
API Tests/BGApp/Collections/folder.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
info:
|
||||
name: Collections
|
||||
type: folder
|
||||
seq: 6
|
||||
|
||||
request:
|
||||
auth: inherit
|
||||
@@ -17,7 +17,7 @@ config:
|
||||
request:
|
||||
auth:
|
||||
type: bearer
|
||||
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJlalJlIiwiY2xhaW1zIjpbIkFETUlOIiwiVVNFUlNfQ1JFQVRFIiwiVVNFUlNfU0VMRl9SRUFEIiwiVVNFUlNfU0VMRl9VUERBVEUiLCJVU0VSU19TRUxGX0RFTEVURSIsIlVTRVJTX09USEVSX1JFQUQiLCJVU0VSU19PVEhFUl9VUERBVEUiXSwiaWF0IjoxNzcxNjE4NTQzLCJleHAiOjE4MDMxNTQ1NDN9.R-3Qb5CEcLJBSt7DnsO9b0IGRVYDIZuFfH1m9TikVXU
|
||||
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJNUFJLTFIiLCJjbGFpbXMiOlsiQURNSU4iLCJVU0VSU19DUkVBVEUiLCJVU0VSU19TRUxGX1JFQUQiLCJVU0VSU19TRUxGX1VQREFURSIsIlVTRVJTX1NFTEZfREVMRVRFIiwiVVNFUlNfT1RIRVJfUkVBRCIsIlVTRVJTX09USEVSX1VQREFURSJdLCJpYXQiOjE3NzE2ODcxNzAsImV4cCI6MTgwMzIyMzE3MH0.inf1q3LTMuTkzLI-lEezYduPCpidJDaqsWZNNIY_doE
|
||||
actions:
|
||||
- type: set-variable
|
||||
phase: after-response
|
||||
|
||||
@@ -3,12 +3,13 @@ import jwt from 'jsonwebtoken';
|
||||
import { UnwrappedRequest } from '../utilities/guard';
|
||||
import { ErrorResponse, OkResponse, UnauthorizedResponse } from '../utilities/responseHelper';
|
||||
import { Claims } from '../orm/claims';
|
||||
import { ChangePasswordRequest, LoginRequest, SecureId } from '../utilities/requestModels';
|
||||
import { ChangePasswordRequest, LoginRequest } from '../utilities/requestModels';
|
||||
import { UserId } from '../utilities/secureIds';
|
||||
|
||||
async function login(request: UnwrappedRequest<LoginRequest>): Promise<Response> {
|
||||
try {
|
||||
const verify: {
|
||||
userId: SecureId;
|
||||
userId: UserId;
|
||||
refreshCount: string;
|
||||
} | null = await orm.users.verifyCredentials(request.body.email, request.body.password);
|
||||
if (!verify) {
|
||||
@@ -53,7 +54,7 @@ async function token(request: UnwrappedRequest): Promise<Response> {
|
||||
r: string;
|
||||
} = jwt.verify(refreshCookie, process.env.JWT_SECRET_KEY as string) as { u: string; r: string };
|
||||
|
||||
if (!(await orm.users.verifyRefreshCount(SecureId.fromID(refreshToken.u), refreshToken.r))) {
|
||||
if (!(await orm.users.verifyRefreshCount(UserId.fromID(refreshToken.u), refreshToken.r))) {
|
||||
const response = new UnauthorizedResponse('Invalid refresh token');
|
||||
response.headers.set('Clear-Site-Data', '"cookies","cache","storage","executionContexts"');
|
||||
return response;
|
||||
@@ -84,7 +85,7 @@ async function changePassword(request: UnwrappedRequest<ChangePasswordRequest>):
|
||||
try {
|
||||
return new OkResponse(
|
||||
await orm.users.changePassword(
|
||||
SecureId.fromHash(request.params.id),
|
||||
UserId.fromHash(request.params.id),
|
||||
request.body.oldPassword,
|
||||
request.body.newPassword,
|
||||
request.claims,
|
||||
|
||||
56
src/endpoints/collections.ts
Normal file
56
src/endpoints/collections.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { orm } from '../orm/orm';
|
||||
import { UnwrappedRequest } from '../utilities/guard';
|
||||
import { CreatedResponse, ErrorResponse, OkResponse } from '../utilities/responseHelper';
|
||||
import { CreateCollectionRequest, UpdateCollectionRequest } from '../utilities/requestModels';
|
||||
import { CollectionId } from '../utilities/secureIds';
|
||||
|
||||
async function create(request: UnwrappedRequest<CreateCollectionRequest>): Promise<Response> {
|
||||
try {
|
||||
const newPlayer = await orm.collections.create(request.body, request.claims);
|
||||
return new CreatedResponse(newPlayer);
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
async function get(request: UnwrappedRequest): Promise<Response> {
|
||||
try {
|
||||
return new OkResponse(await orm.collections.get(CollectionId.fromHash(request.params.id), request.claims));
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
async function list(request: UnwrappedRequest): Promise<Response> {
|
||||
try {
|
||||
return new OkResponse(await orm.collections.list(request.claims));
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
async function update(request: UnwrappedRequest<UpdateCollectionRequest>): Promise<Response> {
|
||||
try {
|
||||
return new OkResponse(
|
||||
await orm.collections.update(CollectionId.fromHash(request.params.id), request.body, request.claims),
|
||||
);
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
async function drop(request: UnwrappedRequest): Promise<Response> {
|
||||
try {
|
||||
return new OkResponse(await orm.collections.drop(CollectionId.fromHash(request.params.id), request.claims));
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
create,
|
||||
get,
|
||||
list,
|
||||
update,
|
||||
drop,
|
||||
};
|
||||
@@ -3,9 +3,9 @@ import { UnwrappedRequest } from '../utilities/guard';
|
||||
import { CreatedResponse, ErrorResponse, OkResponse } from '../utilities/responseHelper';
|
||||
import {
|
||||
CreateGameRequest,
|
||||
SecureId,
|
||||
UpdateGameRequest,
|
||||
} from '../utilities/requestModels';
|
||||
import { GameId } from '../utilities/secureIds';
|
||||
|
||||
async function create(request: UnwrappedRequest<CreateGameRequest>): Promise<Response> {
|
||||
try {
|
||||
@@ -25,7 +25,7 @@ async function create(request: UnwrappedRequest<CreateGameRequest>): Promise<Res
|
||||
|
||||
async function get(request: UnwrappedRequest): Promise<Response> {
|
||||
try {
|
||||
return new OkResponse(await orm.games.get(SecureId.fromHash(request.params.id)));
|
||||
return new OkResponse(await orm.games.get(GameId.fromHash(request.params.id)));
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
}
|
||||
@@ -35,7 +35,7 @@ async function update(request: UnwrappedRequest<UpdateGameRequest>): Promise<Res
|
||||
try {
|
||||
return new OkResponse(
|
||||
await orm.games.update(
|
||||
SecureId.fromHash(request.params.id),
|
||||
GameId.fromHash(request.params.id),
|
||||
request.body,
|
||||
request.claims,
|
||||
),
|
||||
@@ -47,7 +47,7 @@ async function update(request: UnwrappedRequest<UpdateGameRequest>): Promise<Res
|
||||
|
||||
async function drop(request: UnwrappedRequest): Promise<Response> {
|
||||
try {
|
||||
return new OkResponse(await orm.games.drop(SecureId.fromHash(request.params.id)));
|
||||
return new OkResponse(await orm.games.drop(GameId.fromHash(request.params.id)));
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
}
|
||||
|
||||
@@ -4,16 +4,16 @@ import { CreatedResponse, ErrorResponse } from '../utilities/responseHelper';
|
||||
import {
|
||||
AcceptInviteRequest,
|
||||
InviteUserRequest,
|
||||
SecureId,
|
||||
} from '../utilities/requestModels';
|
||||
import { PlayerId, UserId } from '../utilities/secureIds';
|
||||
|
||||
async function create(request: UnwrappedRequest<InviteUserRequest>): Promise<Response> {
|
||||
try {
|
||||
const newUser = await orm.invites.create(
|
||||
{
|
||||
...request.body,
|
||||
playerId: SecureId.fromHash(request.body.playerId),
|
||||
invitedByUserId: request.claims.userId as SecureId,
|
||||
playerId: PlayerId.fromHash(request.body.playerId),
|
||||
invitedByUserId: request.claims.userId as UserId,
|
||||
},
|
||||
);
|
||||
return new CreatedResponse(newUser);
|
||||
@@ -1,11 +1,12 @@
|
||||
import { orm } from '../orm/orm';
|
||||
import { UnwrappedRequest } from '../utilities/guard';
|
||||
import { CreatedResponse, ErrorResponse, OkResponse } from '../utilities/responseHelper';
|
||||
import { CreatePlayerRequest, SecureId, UpdatePlayerRequest } from '../utilities/requestModels';
|
||||
import { CreatePlayerRequest, UpdatePlayerRequest } from '../utilities/requestModels';
|
||||
import { PlayerId } from '../utilities/secureIds';
|
||||
|
||||
async function create(request: UnwrappedRequest<CreatePlayerRequest>): Promise<Response> {
|
||||
try {
|
||||
const newPlayer = await orm.players.create(request.body, request.claims);
|
||||
const newPlayer = await orm.players.create(request.body);
|
||||
return new CreatedResponse(newPlayer);
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
@@ -14,7 +15,7 @@ async function create(request: UnwrappedRequest<CreatePlayerRequest>): Promise<R
|
||||
|
||||
async function get(request: UnwrappedRequest): Promise<Response> {
|
||||
try {
|
||||
return new OkResponse(await orm.players.get(SecureId.fromHash(request.params.id), request.claims));
|
||||
return new OkResponse(await orm.players.get(PlayerId.fromHash(request.params.id), request.claims));
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
}
|
||||
@@ -31,7 +32,7 @@ async function list(request: UnwrappedRequest): Promise<Response> {
|
||||
async function update(request: UnwrappedRequest<UpdatePlayerRequest>): Promise<Response> {
|
||||
try {
|
||||
return new OkResponse(
|
||||
await orm.players.update(SecureId.fromHash(request.params.id), request.body, request.claims),
|
||||
await orm.players.update(PlayerId.fromHash(request.params.id), request.body, request.claims),
|
||||
);
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
@@ -40,7 +41,7 @@ async function update(request: UnwrappedRequest<UpdatePlayerRequest>): Promise<R
|
||||
|
||||
async function drop(request: UnwrappedRequest): Promise<Response> {
|
||||
try {
|
||||
return new OkResponse(await orm.players.drop(SecureId.fromHash(request.params.id), request.claims));
|
||||
return new OkResponse(await orm.players.drop(PlayerId.fromHash(request.params.id), request.claims));
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
import { orm } from '../orm/orm';
|
||||
import { UnwrappedRequest } from '../utilities/guard';
|
||||
import { CreatedResponse, ErrorResponse, OkResponse } from '../utilities/responseHelper';
|
||||
import { CreateUserRequest, SecureId, UpdateUserRequest } from '../utilities/requestModels';
|
||||
import { CreateUserRequest, UpdateUserRequest } from '../utilities/requestModels';
|
||||
import { PlayerId, UserId } from '../utilities/secureIds';
|
||||
|
||||
async function create(request: UnwrappedRequest<CreateUserRequest>): Promise<Response> {
|
||||
try {
|
||||
const newUser = await orm.users.create(
|
||||
{
|
||||
...request.body,
|
||||
playerId: SecureId.fromHash(request.body.playerId),
|
||||
},
|
||||
request.claims,
|
||||
playerId: PlayerId.fromHash(request.body.playerId),
|
||||
}
|
||||
);
|
||||
return new CreatedResponse(newUser);
|
||||
} catch (error: any) {
|
||||
@@ -20,7 +20,7 @@ async function create(request: UnwrappedRequest<CreateUserRequest>): Promise<Res
|
||||
|
||||
async function get(request: UnwrappedRequest): Promise<Response> {
|
||||
try {
|
||||
return new OkResponse(await orm.users.get(SecureId.fromHash(request.params.id), request.claims));
|
||||
return new OkResponse(await orm.users.get(UserId.fromHash(request.params.id), request.claims));
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
}
|
||||
@@ -29,7 +29,7 @@ async function get(request: UnwrappedRequest): Promise<Response> {
|
||||
async function update(request: UnwrappedRequest<UpdateUserRequest>): Promise<Response> {
|
||||
try {
|
||||
return new OkResponse(
|
||||
await orm.users.update(SecureId.fromHash(request.params.id), request.body, request.claims),
|
||||
await orm.users.update(UserId.fromHash(request.params.id), request.body, request.claims),
|
||||
);
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
@@ -38,7 +38,7 @@ async function update(request: UnwrappedRequest<UpdateUserRequest>): Promise<Res
|
||||
|
||||
async function drop(request: UnwrappedRequest): Promise<Response> {
|
||||
try {
|
||||
return new OkResponse(await orm.users.drop(SecureId.fromHash(request.params.id), request.claims));
|
||||
return new OkResponse(await orm.users.drop(UserId.fromHash(request.params.id), request.claims));
|
||||
} catch (error: any) {
|
||||
return new ErrorResponse(error as Error);
|
||||
}
|
||||
20
src/index.ts
20
src/index.ts
@@ -1,17 +1,19 @@
|
||||
import auth from './routes/auth';
|
||||
import user from './routes/user';
|
||||
import player from './routes/player';
|
||||
import game from './routes/game';
|
||||
import { OkResponse } from './utilities/responseHelper';
|
||||
import invite from './routes/invite';
|
||||
import auth from './routes/auth';
|
||||
import users from './routes/users';
|
||||
import players from './routes/players';
|
||||
import games from './routes/games';
|
||||
import invites from './routes/invites';
|
||||
import collections from './routes/collections';
|
||||
|
||||
const server = Bun.serve({
|
||||
routes: {
|
||||
...auth,
|
||||
...user,
|
||||
...player,
|
||||
...game,
|
||||
...invite,
|
||||
...users,
|
||||
...players,
|
||||
...games,
|
||||
...invites,
|
||||
...collections,
|
||||
'/test': {
|
||||
GET: () => {
|
||||
return new OkResponse();
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { sql } from 'bun';
|
||||
import { ClaimDefinition } from '../utilities/claimDefinitions';
|
||||
import { SecureId } from '../utilities/requestModels';
|
||||
import { SecureId, UserId } from '../utilities/secureIds';
|
||||
|
||||
export class Claims extends ClaimDefinition {
|
||||
userId?: SecureId;
|
||||
userId?: UserId;
|
||||
claims: string[] = [];
|
||||
|
||||
constructor(raw?:{userId?:string, claims?: string[]}) {
|
||||
constructor(raw?: { userId?: string; claims?: string[] }) {
|
||||
super();
|
||||
this.userId = raw?.userId ? SecureId.fromHash(raw.userId) : undefined;
|
||||
this.userId = raw?.userId ? UserId.fromHash(raw.userId) : undefined;
|
||||
this.claims = raw?.claims ?? [];
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ export class ClaimsOrm {
|
||||
JOIN claims as c on uc.claim_id = c.id
|
||||
where uc.user_id = ${userId};`;
|
||||
const claims = new Claims();
|
||||
claims.userId = SecureId.fromID(userId);
|
||||
claims.userId = UserId.fromID(userId);
|
||||
claims.claims = dbResults.map((x) => x.name);
|
||||
return claims;
|
||||
}
|
||||
|
||||
130
src/orm/collections.ts
Normal file
130
src/orm/collections.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { Claims } from './claims';
|
||||
import { sql } from 'bun';
|
||||
import { first } from 'lodash';
|
||||
import { NotFoundError, UnauthorizedError } from '../utilities/errors';
|
||||
import { UpdateCollectionRequest } from '../utilities/requestModels';
|
||||
import { Game } from './games';
|
||||
import { CollectionId, GameId } from '../utilities/secureIds';
|
||||
|
||||
export class Collection {
|
||||
id: CollectionId;
|
||||
name: string;
|
||||
games: Game[];
|
||||
|
||||
constructor(input: { id: CollectionId; name: string; games?: Game[] }) {
|
||||
this.id = input.id;
|
||||
this.name = input?.name;
|
||||
this.games = input.games ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
export class CollectionsOrm {
|
||||
async create(model: { name: string }, claims: Claims): Promise<Collection> {
|
||||
await sql`INSERT INTO collections (name, user_id)
|
||||
VALUES (${model.name}, ${claims?.userId?.raw})`;
|
||||
const newPCollectionId: string = (first(await sql`SELECT lastval();`) as any)?.lastval as string;
|
||||
|
||||
return await this.get(CollectionId.fromID(newPCollectionId));
|
||||
}
|
||||
|
||||
async get(id: CollectionId, claims?: Claims): Promise<Collection> {
|
||||
const dbResult: any = await sql`SELECT
|
||||
c.id AS collection_id,
|
||||
c.name AS collection_name,
|
||||
c.user_id AS user_id,
|
||||
g.id AS game_id,
|
||||
g.name AS game_name
|
||||
FROM collections c
|
||||
LEFT JOIN collection_games cg ON cg.collection_id = c.id
|
||||
LEFT JOIN games g ON g.id = cg.game_id
|
||||
WHERE c.id = ${id.raw}`;
|
||||
|
||||
if (!(Claims.test(Claims.ADMIN, claims) || Claims.test(Claims.COLLECTIONS.UNOWNED.READ, claims))) {
|
||||
throw new UnauthorizedError();
|
||||
} else if (
|
||||
Claims.test(Claims.COLLECTIONS.OWNED.READ, claims) &&
|
||||
claims?.userId &&
|
||||
dbResult?.[0]?.user_id !== claims.userId.raw
|
||||
) {
|
||||
throw new UnauthorizedError();
|
||||
}
|
||||
|
||||
if (!dbResult?.length) {
|
||||
throw new NotFoundError('No matching player exists');
|
||||
}
|
||||
|
||||
return new Collection({
|
||||
id: CollectionId.fromID(dbResult[0].collection_id),
|
||||
name: dbResult[0].collection_name,
|
||||
games: dbResult
|
||||
.filter((x: { game_id: string; game_name: string }) => x.game_id)
|
||||
.map(
|
||||
(x: { game_id: string; game_name: string }) =>
|
||||
new Game({
|
||||
id: GameId.fromID(x.game_id),
|
||||
name: x.game_name,
|
||||
}),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
async list(claims?: Claims): Promise<Collection[]> {
|
||||
if (!claims || Claims.test(Claims.ADMIN, claims)) {
|
||||
return (await sql`SELECT * FROM collections`).map(
|
||||
(x: { id: string; name: string }) =>
|
||||
new Collection({
|
||||
id: CollectionId.fromID(x.id),
|
||||
name: x.name,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (!Claims.test(Claims.COLLECTIONS.OWNED.LIST, claims)) {
|
||||
throw new UnauthorizedError();
|
||||
}
|
||||
|
||||
return (await sql`SELECT * FROM collections WHERE user_id=${claims.userId?.raw}`).map(
|
||||
(x: { id: string; name: string }) =>
|
||||
new Collection({
|
||||
id: CollectionId.fromID(x.id),
|
||||
name: x.name,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async update(id: CollectionId, patch: UpdateCollectionRequest, claims?: Claims): Promise<Collection> {
|
||||
const collection = await this.get(id);
|
||||
|
||||
if (!(Claims.test(Claims.ADMIN, claims) || Claims.test(Claims.COLLECTIONS.UNOWNED.UPDATE, claims))) {
|
||||
throw new UnauthorizedError();
|
||||
} else if (
|
||||
Claims.test(Claims.COLLECTIONS.OWNED.UPDATE, claims) &&
|
||||
claims?.userId &&
|
||||
collection.id !== claims.userId
|
||||
) {
|
||||
throw new UnauthorizedError();
|
||||
}
|
||||
collection.name = patch.name ?? collection.name;
|
||||
|
||||
await sql`UPDATE collections SET name=${collection.name} WHERE id=${id.raw}`;
|
||||
|
||||
return await this.get(id);
|
||||
}
|
||||
|
||||
async drop(id: CollectionId, claims?: Claims): Promise<void> {
|
||||
const collection = await this.get(id);
|
||||
if (!(Claims.test(Claims.ADMIN, claims) || Claims.test(Claims.COLLECTIONS.UNOWNED.DELETE, claims))) {
|
||||
throw new UnauthorizedError();
|
||||
} else if (
|
||||
Claims.test(Claims.COLLECTIONS.OWNED.DELETE, claims) &&
|
||||
claims?.userId &&
|
||||
collection.id !== claims.userId
|
||||
) {
|
||||
throw new UnauthorizedError();
|
||||
}
|
||||
|
||||
await sql`DELETE FROM collections WHERE id=${id.raw}`;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
import { Claims } from './claims';
|
||||
import { sql } from 'bun';
|
||||
import { first } from 'lodash';
|
||||
import { NotFoundError, UnauthorizedError } from '../utilities/errors';
|
||||
import { CreateGameRequest, SecureId, UpdateGameRequest } from '../utilities/requestModels';
|
||||
import { NotFoundError } from '../utilities/errors';
|
||||
import { CreateGameRequest, UpdateGameRequest } from '../utilities/requestModels';
|
||||
import { memo } from '../utilities/helpers';
|
||||
import { GameId } from '../utilities/secureIds';
|
||||
|
||||
export class Game {
|
||||
id: SecureId;
|
||||
id: GameId;
|
||||
name: string;
|
||||
imagePath?: string;
|
||||
bggId?: string;
|
||||
|
||||
constructor(input: { id: SecureId; name: string; imagePath?: string; bggId?: string }) {
|
||||
constructor(input: { id: GameId; name: string; imagePath?: string; bggId?: string }) {
|
||||
this.id = input.id;
|
||||
this.name = input?.name;
|
||||
this.imagePath = input?.imagePath;
|
||||
@@ -25,10 +26,10 @@ export class GamesOrm {
|
||||
VALUES (${model.name}, ${Claims.test(Claims.GAMES.MANAGE_IMAGES, claims) ? model.imagePath : null}, ${model.bggId})`;
|
||||
const newGameId: string = (first(await sql`SELECT lastval();`) as any)?.lastval as string;
|
||||
|
||||
return await this.get(SecureId.fromID(newGameId));
|
||||
return await this.get(GameId.fromID(newGameId));
|
||||
}
|
||||
|
||||
async get(id: SecureId): Promise<Game> {
|
||||
async get(id: GameId): Promise<Game> {
|
||||
const dbResult: any = first(
|
||||
await sql`SELECT *
|
||||
FROM games
|
||||
@@ -41,14 +42,14 @@ export class GamesOrm {
|
||||
}
|
||||
|
||||
return new Game({
|
||||
id: SecureId.fromID(dbResult.id),
|
||||
id: GameId.fromID(dbResult.id),
|
||||
name: dbResult.name,
|
||||
bggId: dbResult.bgg_id,
|
||||
imagePath: dbResult.image_path,
|
||||
});
|
||||
}
|
||||
|
||||
async update(id: SecureId, patch: UpdateGameRequest, claims?: Claims): Promise<Game> {
|
||||
async update(id: GameId, patch: UpdateGameRequest, claims?: Claims): Promise<Game> {
|
||||
const gameToUpdate = await this.get(id);
|
||||
gameToUpdate.name = patch.name ?? gameToUpdate.name;
|
||||
gameToUpdate.bggId = patch.bggId ?? gameToUpdate.bggId;
|
||||
@@ -66,7 +67,7 @@ export class GamesOrm {
|
||||
return await this.get(id);
|
||||
}
|
||||
|
||||
async drop(id: SecureId): Promise<void> {
|
||||
async drop(id: GameId): Promise<void> {
|
||||
// Ensure player exists before attempting to delete
|
||||
await this.get(id);
|
||||
await sql.transaction(async (tx) => {
|
||||
@@ -103,7 +104,7 @@ export class GamesOrm {
|
||||
return dbResult.map(
|
||||
(x: { id: string; name: string }) =>
|
||||
new Game({
|
||||
id: SecureId.fromID(x.id),
|
||||
id: GameId.fromID(x.id),
|
||||
name: x.name,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { sql } from 'bun';
|
||||
import { first } from 'lodash';
|
||||
import { BadRequestError, InternalServerError, NotFoundError, UnauthorizedError } from '../utilities/errors';
|
||||
import { SecureId } from '../utilities/requestModels';
|
||||
import { PlayerId, UserId } from '../utilities/secureIds';
|
||||
import { createRandomString } from '../utilities/helpers';
|
||||
import { Resend } from 'resend';
|
||||
import { orm } from './orm';
|
||||
@@ -17,8 +17,8 @@ export class InvitesOrm {
|
||||
invitedByUserId,
|
||||
}: {
|
||||
email: string;
|
||||
playerId: SecureId;
|
||||
invitedByUserId: SecureId;
|
||||
playerId: PlayerId;
|
||||
invitedByUserId: UserId;
|
||||
},
|
||||
claims?: Claims,
|
||||
): Promise<void> {
|
||||
@@ -120,7 +120,7 @@ export class InvitesOrm {
|
||||
|
||||
const createdUser = await orm.users.create({
|
||||
email: invite.email,
|
||||
playerId: SecureId.fromID(invite.player_id),
|
||||
playerId: PlayerId.fromID(invite.player_id),
|
||||
password,
|
||||
});
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { UsersOrm } from './user';
|
||||
import { PlayersOrm } from './players';
|
||||
import { GamesOrm } from './games';
|
||||
import { InvitesOrm } from './invites';
|
||||
import { CollectionsOrm } from './collections';
|
||||
|
||||
class Orm {
|
||||
readonly claims: ClaimsOrm = new ClaimsOrm();
|
||||
@@ -10,6 +11,7 @@ class Orm {
|
||||
readonly players: PlayersOrm = new PlayersOrm();
|
||||
readonly games: GamesOrm = new GamesOrm();
|
||||
readonly invites: InvitesOrm = new InvitesOrm();
|
||||
readonly collections: CollectionsOrm = new CollectionsOrm();
|
||||
}
|
||||
|
||||
export const orm = new Orm();
|
||||
|
||||
@@ -3,17 +3,18 @@ import { sql } from 'bun';
|
||||
import { first } from 'lodash';
|
||||
import { NotFoundError, UnauthorizedError } from '../utilities/errors';
|
||||
import { orm } from './orm';
|
||||
import { SecureId, UpdatePlayerRequest } from '../utilities/requestModels';
|
||||
import { UpdatePlayerRequest } from '../utilities/requestModels';
|
||||
import { PlayerId } from '../utilities/secureIds';
|
||||
|
||||
export class Player {
|
||||
id: SecureId;
|
||||
id: PlayerId;
|
||||
name: string;
|
||||
elo: number;
|
||||
isRatingLocked: boolean;
|
||||
canBeMultiple: boolean;
|
||||
|
||||
constructor(input: {
|
||||
id: SecureId;
|
||||
id: PlayerId;
|
||||
name: string;
|
||||
elo?: number;
|
||||
isRatingLocked?: boolean;
|
||||
@@ -28,15 +29,15 @@ export class Player {
|
||||
}
|
||||
|
||||
export class PlayersOrm {
|
||||
async create(model: { name: string }, claims?: Claims): Promise<Player> {
|
||||
async create(model: { name: string }): Promise<Player> {
|
||||
await sql`INSERT INTO players (name)
|
||||
VALUES (${model.name})`;
|
||||
const newPlayerId: string = (first(await sql`SELECT lastval();`) as any)?.lastval as string;
|
||||
|
||||
return await this.get(SecureId.fromID(newPlayerId));
|
||||
return await this.get(PlayerId.fromID(newPlayerId));
|
||||
}
|
||||
|
||||
async get(id: SecureId, claims?: Claims): Promise<Player> {
|
||||
async get(id: PlayerId, claims?: Claims): Promise<Player> {
|
||||
if (!(Claims.test(Claims.ADMIN, claims) || Claims.test(Claims.PLAYERS.OTHER.READ, claims))) {
|
||||
throw new UnauthorizedError();
|
||||
} else if (Claims.test(Claims.PLAYERS.SELF.READ, claims) && claims?.userId) {
|
||||
@@ -57,7 +58,7 @@ export class PlayersOrm {
|
||||
}
|
||||
|
||||
return new Player({
|
||||
id: SecureId.fromID(dbResult.id),
|
||||
id: PlayerId.fromID(dbResult.id),
|
||||
name: dbResult.name,
|
||||
elo: parseInt(dbResult.elo),
|
||||
isRatingLocked: dbResult.is_rating_locked,
|
||||
@@ -70,7 +71,7 @@ export class PlayersOrm {
|
||||
return (await sql`SELECT * FROM players`).map(
|
||||
(x: { id: string; name: string; elo: string; is_rating_locked: boolean; can_be_multiple: boolean }) =>
|
||||
new Player({
|
||||
id: SecureId.fromID(x.id),
|
||||
id: PlayerId.fromID(x.id),
|
||||
name: x.name,
|
||||
elo: parseInt(x.elo),
|
||||
isRatingLocked: x.is_rating_locked,
|
||||
@@ -98,7 +99,7 @@ export class PlayersOrm {
|
||||
).map(
|
||||
(x: { id: string; name: string; elo: string; is_rating_locked: boolean; can_be_multiple: boolean }) =>
|
||||
new Player({
|
||||
id: SecureId.fromID(x.id),
|
||||
id: PlayerId.fromID(x.id),
|
||||
name: x.name,
|
||||
elo: parseInt(x.elo),
|
||||
isRatingLocked: x.is_rating_locked,
|
||||
@@ -107,7 +108,7 @@ export class PlayersOrm {
|
||||
);
|
||||
}
|
||||
|
||||
async update(id: SecureId, patch: UpdatePlayerRequest, claims?: Claims): Promise<Player> {
|
||||
async update(id: PlayerId, patch: UpdatePlayerRequest, claims?: Claims): Promise<Player> {
|
||||
if (!(Claims.test(Claims.ADMIN, claims) || Claims.test(Claims.PLAYERS.OTHER.UPDATE, claims))) {
|
||||
throw new UnauthorizedError();
|
||||
} else if (Claims.test(Claims.PLAYERS.SELF.UPDATE, claims) && claims?.userId) {
|
||||
@@ -131,7 +132,7 @@ export class PlayersOrm {
|
||||
return await this.get(id);
|
||||
}
|
||||
|
||||
async drop(id: SecureId, claims?: Claims): Promise<void> {
|
||||
async drop(id: PlayerId, claims?: Claims): Promise<void> {
|
||||
if (!(Claims.test(Claims.ADMIN, claims) || Claims.test(Claims.PLAYERS.OTHER.DELETE, claims))) {
|
||||
throw new UnauthorizedError();
|
||||
} else if (Claims.test(Claims.PLAYERS.SELF.DELETE, claims) && claims?.userId) {
|
||||
|
||||
@@ -3,17 +3,18 @@ import { sql } from 'bun';
|
||||
import { first } from 'lodash';
|
||||
import argon2 from 'argon2';
|
||||
import { BadRequestError, NotFoundError, UnauthorizedError } from '../utilities/errors';
|
||||
import { SecureId, UpdateUserRequest } from '../utilities/requestModels';
|
||||
import { UpdateUserRequest } from '../utilities/requestModels';
|
||||
import { orm } from './orm';
|
||||
import { PlayerId, UserId } from '../utilities/secureIds';
|
||||
|
||||
export class User {
|
||||
id: SecureId;
|
||||
playerId: SecureId;
|
||||
id: UserId;
|
||||
playerId: PlayerId;
|
||||
email: string;
|
||||
isAdmin: boolean;
|
||||
isActive: boolean;
|
||||
|
||||
constructor(id: SecureId, playerId: SecureId, email: string, isAdmin: boolean = false, isActive: boolean = true) {
|
||||
constructor(id: UserId, playerId: PlayerId, email: string, isAdmin: boolean = false, isActive: boolean = true) {
|
||||
this.id = id;
|
||||
this.playerId = playerId;
|
||||
this.email = email;
|
||||
@@ -24,8 +25,7 @@ export class User {
|
||||
|
||||
export class UsersOrm {
|
||||
async create(
|
||||
{ email, password, playerId }: { email: string; password: string; playerId: SecureId },
|
||||
claims?: Claims,
|
||||
{ email, password, playerId }: { email: string; password: string; playerId: PlayerId },
|
||||
): Promise<User> {
|
||||
const existingUser: any = first(
|
||||
await sql`SELECT id
|
||||
@@ -41,7 +41,7 @@ export class UsersOrm {
|
||||
const passwordHash = await argon2.hash(password);
|
||||
await sql`INSERT INTO users (email, pass_hash, player_id)
|
||||
VALUES (${email}, ${passwordHash}, ${playerId.raw})`;
|
||||
const newUserId: SecureId = SecureId.fromID((first(await sql`SELECT lastval();`) as any)?.lastval as string);
|
||||
const newUserId: UserId = UserId.fromID((first(await sql`SELECT lastval();`) as any)?.lastval as string);
|
||||
await sql.transaction(async (tx) => {
|
||||
for (let i in defaultClaims) {
|
||||
await tx`INSERT INTO user_claims (user_id, claim_id)
|
||||
@@ -52,7 +52,7 @@ export class UsersOrm {
|
||||
return await this.get(newUserId);
|
||||
}
|
||||
|
||||
async get(id: SecureId, claims?: Claims): Promise<User> {
|
||||
async get(id: UserId, claims?: Claims): Promise<User> {
|
||||
if (
|
||||
!(
|
||||
Claims.test(Claims.ADMIN, claims) ||
|
||||
@@ -76,14 +76,14 @@ export class UsersOrm {
|
||||
}
|
||||
|
||||
return new User(
|
||||
SecureId.fromID(dbResult.id),
|
||||
SecureId.fromID(dbResult.player_id),
|
||||
UserId.fromID(dbResult.id),
|
||||
PlayerId.fromID(dbResult.player_id),
|
||||
dbResult.email,
|
||||
dbResult.is_admin,
|
||||
);
|
||||
}
|
||||
|
||||
async update(id: SecureId, patch: UpdateUserRequest, claims?: Claims): Promise<User> {
|
||||
async update(id: UserId, patch: UpdateUserRequest, claims?: Claims): Promise<User> {
|
||||
if (
|
||||
!(
|
||||
Claims.test(Claims.ADMIN, claims) ||
|
||||
@@ -108,7 +108,7 @@ export class UsersOrm {
|
||||
return await this.get(id);
|
||||
}
|
||||
|
||||
async drop(id: SecureId, claims?: Claims): Promise<void> {
|
||||
async drop(id: UserId, claims?: Claims): Promise<void> {
|
||||
if (
|
||||
!(
|
||||
Claims.test(Claims.ADMIN, claims) ||
|
||||
@@ -136,7 +136,7 @@ export class UsersOrm {
|
||||
async verifyCredentials(
|
||||
email: string,
|
||||
password: string,
|
||||
): Promise<{ userId: SecureId; refreshCount: string } | null> {
|
||||
): Promise<{ userId: UserId; refreshCount: string } | null> {
|
||||
const dbResult: any = first(
|
||||
await sql`SELECT *
|
||||
FROM users
|
||||
@@ -153,12 +153,12 @@ export class UsersOrm {
|
||||
}
|
||||
|
||||
return {
|
||||
userId: SecureId.fromID(dbResult.id),
|
||||
userId: UserId.fromID(dbResult.id),
|
||||
refreshCount: dbResult.refresh_count,
|
||||
};
|
||||
}
|
||||
|
||||
async verifyRefreshCount(id: SecureId, refreshCount: string): Promise<boolean> {
|
||||
async verifyRefreshCount(id: UserId, refreshCount: string): Promise<boolean> {
|
||||
const dbResult: any = first(
|
||||
await sql`SELECT *
|
||||
FROM users
|
||||
@@ -169,7 +169,7 @@ export class UsersOrm {
|
||||
}
|
||||
|
||||
async changePassword(
|
||||
id: SecureId,
|
||||
id: UserId,
|
||||
oldPassword: string | null,
|
||||
newPassword: string,
|
||||
claims?: Claims,
|
||||
|
||||
17
src/routes/collections.ts
Normal file
17
src/routes/collections.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { guard } from '../utilities/guard';
|
||||
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': {
|
||||
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]),
|
||||
},
|
||||
'/api/collection/list': {
|
||||
GET: guard(collections.list, [Claims.ADMIN, Claims.COLLECTIONS.OWNED.LIST]),
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { guard, unwrapMethod } from '../utilities/guard';
|
||||
import { Claims } from '../orm/claims';
|
||||
import invite from '../endpoints/invite';
|
||||
import invite from '../endpoints/invites';
|
||||
|
||||
export default {
|
||||
'/api/invite': {
|
||||
@@ -1,6 +1,6 @@
|
||||
import { guard } from '../utilities/guard';
|
||||
import { Claims } from '../orm/claims';
|
||||
import player from '../endpoints/player';
|
||||
import player from '../endpoints/players';
|
||||
|
||||
export default {
|
||||
'/api/player': {
|
||||
@@ -1,17 +1,11 @@
|
||||
import { guard, unwrap, unwrapMethod } from '../utilities/guard';
|
||||
import user from '../endpoints/user';
|
||||
import { guard } from '../utilities/guard';
|
||||
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/invite': {
|
||||
POST: guard(user.invite, [Claims.ADMIN, Claims.USERS.INVITE]),
|
||||
},
|
||||
'/api/user/invite/accept': {
|
||||
POST: unwrapMethod(user.acceptInvite),
|
||||
},
|
||||
'/api/user/: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]),
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect, test } from 'bun:test';
|
||||
import user from '../endpoints/user';
|
||||
import user from '../endpoints/users';
|
||||
import { UnwrappedRequest } from '../utilities/guard';
|
||||
import { Claims } from '../orm/claims';
|
||||
import { orm } from '../orm/orm';
|
||||
|
||||
@@ -107,6 +107,7 @@ export class ClaimDefinition {
|
||||
READ: 'COLLECTIONS_OWNED_READ',
|
||||
UPDATE: 'COLLECTIONS_OWNED_UPDATE',
|
||||
DELETE: 'COLLECTIONS_OWNED_DELETE',
|
||||
LIST: 'COLLECTIONS_OWNED_LIST',
|
||||
COMMENTS: {
|
||||
DELETE: 'COLLECTIONS_OWNED_COMMENTS_DELETE',
|
||||
},
|
||||
|
||||
@@ -21,3 +21,8 @@ export class NotFoundError extends Error {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
export class NotImplementedError extends Error {
|
||||
constructor(message?: string | undefined) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,6 @@ import { BunRequest as Request } from 'bun';
|
||||
import jwt, { TokenExpiredError } from 'jsonwebtoken';
|
||||
import { ErrorResponse, UnauthorizedResponse } from './responseHelper';
|
||||
import { Claims } from '../orm/claims';
|
||||
import HashIds from 'hashids';
|
||||
|
||||
export const hashIds = new HashIds(process.env.JWT_SECRET, 4);
|
||||
|
||||
export function guardRedirect(
|
||||
method: (request: UnwrappedRequest<any>) => Promise<Response> | Response,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { hashIds } from './guard';
|
||||
|
||||
export interface LoginRequest {
|
||||
email: string;
|
||||
password: string;
|
||||
@@ -43,45 +41,9 @@ export interface UpdateGameRequest {
|
||||
imagePath?: string;
|
||||
bggId?: string;
|
||||
}
|
||||
|
||||
export class SecureId {
|
||||
#hashedValue?: string;
|
||||
#secureValue?: string;
|
||||
get value(): string | undefined {
|
||||
return this.#hashedValue;
|
||||
}
|
||||
set value(value: string) {
|
||||
this.#hashedValue = value;
|
||||
this.#secureValue = hashIds.decode(value)?.toString();
|
||||
}
|
||||
get raw(): string | undefined {
|
||||
return this.#secureValue;
|
||||
}
|
||||
set raw(value: string) {
|
||||
this.#hashedValue = hashIds.encode(value);
|
||||
this.#secureValue = value;
|
||||
}
|
||||
constructor(id: { public?: string; secure?: string }) {
|
||||
if (id.public) {
|
||||
this.value = id.public;
|
||||
} else if (id.secure) {
|
||||
this.raw = id.secure;
|
||||
}
|
||||
}
|
||||
|
||||
toJSON(): string | undefined {
|
||||
return this.#hashedValue;
|
||||
}
|
||||
|
||||
valueOf(): string | undefined {
|
||||
return this.#secureValue;
|
||||
}
|
||||
|
||||
public static fromHash(hash: string) {
|
||||
return new SecureId({ public: hash });
|
||||
}
|
||||
|
||||
public static fromID(id: string) {
|
||||
return new SecureId({ secure: id });
|
||||
export interface CreateCollectionRequest {
|
||||
name: string;
|
||||
}
|
||||
export interface UpdateCollectionRequest {
|
||||
name?: string;
|
||||
}
|
||||
|
||||
124
src/utilities/secureIds.ts
Normal file
124
src/utilities/secureIds.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import HashIds from 'hashids';
|
||||
|
||||
class SecureId {
|
||||
protected static hashPrefix: string = '';
|
||||
protected static get hashScheme(): HashIds {
|
||||
return new HashIds(
|
||||
`${this.hashPrefix}_${process.env.HASHID_SALT_BASE}`,
|
||||
parseInt(process.env.HASHID_LENGTH ?? '6'),
|
||||
process.env.HASHID_ALPHABET,
|
||||
);
|
||||
}
|
||||
|
||||
#hashedValue?: string;
|
||||
#secureValue?: string;
|
||||
#hashScheme: HashIds;
|
||||
|
||||
constructor(id: { public?: string; secure?: string }, hashScheme?: HashIds) {
|
||||
this.#hashScheme = hashScheme ?? (this.constructor as any).hashScheme;
|
||||
|
||||
if (id.public) {
|
||||
this.value = id.public;
|
||||
} else if (id.secure) {
|
||||
this.raw = id.secure;
|
||||
}
|
||||
}
|
||||
get value(): string | undefined {
|
||||
return this.#hashedValue;
|
||||
}
|
||||
set value(value: string) {
|
||||
this.#hashedValue = value;
|
||||
this.#secureValue = this.#hashScheme.decode(value)?.toString();
|
||||
}
|
||||
get raw(): string | undefined {
|
||||
return this.#secureValue;
|
||||
}
|
||||
set raw(value: string) {
|
||||
this.#hashedValue = this.#hashScheme.encode(value);
|
||||
this.#secureValue = value;
|
||||
}
|
||||
|
||||
toJSON(): string | undefined {
|
||||
return this.#hashedValue;
|
||||
}
|
||||
|
||||
valueOf(): string | undefined {
|
||||
return this.#secureValue;
|
||||
}
|
||||
|
||||
public static fromHash<T extends SecureId>(
|
||||
hash: string,
|
||||
type?: { new (id: { public?: string; secure?: string }): T },
|
||||
): SecureId {
|
||||
const t = type ?? SecureId;
|
||||
return new t({ public: hash });
|
||||
}
|
||||
|
||||
public static fromID<T extends SecureId>(
|
||||
id: string,
|
||||
type?: { new (id: { public?: string; secure?: string }): T },
|
||||
): SecureId {
|
||||
const t = type ?? SecureId;
|
||||
return new t({ secure: id });
|
||||
}
|
||||
}
|
||||
|
||||
export class UserId extends SecureId {
|
||||
protected static override hashPrefix: string = 'UserId';
|
||||
|
||||
public static fromHash(hash: string): UserId {
|
||||
return super.fromHash(hash, UserId);
|
||||
}
|
||||
|
||||
public static fromID(id: string): UserId {
|
||||
return super.fromID(id, UserId);
|
||||
}
|
||||
}
|
||||
|
||||
export class PlayerId extends SecureId {
|
||||
protected static override hashPrefix: string = 'PlayerId';
|
||||
|
||||
public static fromHash(hash: string): PlayerId {
|
||||
return super.fromHash(hash, PlayerId);
|
||||
}
|
||||
|
||||
public static fromID(id: string): PlayerId {
|
||||
return super.fromID(id, PlayerId);
|
||||
}
|
||||
}
|
||||
|
||||
export class InviteId extends SecureId {
|
||||
protected static override hashPrefix: string = 'InviteId';
|
||||
|
||||
public static fromHash(hash: string): InviteId {
|
||||
return super.fromHash(hash, InviteId);
|
||||
}
|
||||
|
||||
public static fromID(id: string): InviteId {
|
||||
return super.fromID(id, InviteId);
|
||||
}
|
||||
}
|
||||
|
||||
export class GameId extends SecureId {
|
||||
protected static override hashPrefix: string = 'GameId';
|
||||
|
||||
public static fromHash(hash: string): GameId {
|
||||
return super.fromHash(hash, GameId);
|
||||
}
|
||||
|
||||
public static fromID(id: string): GameId {
|
||||
return super.fromID(id, GameId);
|
||||
}
|
||||
}
|
||||
|
||||
export class CollectionId extends SecureId {
|
||||
protected static override hashPrefix: string = 'CollectionId';
|
||||
|
||||
public static fromHash(hash: string): CollectionId {
|
||||
return super.fromHash(hash, CollectionId);
|
||||
}
|
||||
|
||||
public static fromID(id: string): CollectionId {
|
||||
return super.fromID(id, CollectionId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user