Fixed issues regarding claim verification in ORM
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
#file: noinspection SpellCheckingInspection
|
||||||
name: BGApp
|
name: BGApp
|
||||||
variables:
|
variables:
|
||||||
- name: BEARER_TOKEN
|
- name: BEARER_TOKEN
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#file: noinspection SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection,SpellCheckingInspection
|
||||||
opencollection: 1.0.0
|
opencollection: 1.0.0
|
||||||
|
|
||||||
info:
|
info:
|
||||||
|
|||||||
@@ -1,3 +1,81 @@
|
|||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
|
-- noinspection SpellCheckingInspectionForFile
|
||||||
|
|
||||||
--
|
--
|
||||||
-- PostgreSQL database dump
|
-- PostgreSQL database dump
|
||||||
--
|
--
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ async function login(request: UnwrappedRequest<LoginRequest>): Promise<Response>
|
|||||||
const tokenLifeSpanInDays = 30;
|
const tokenLifeSpanInDays = 30;
|
||||||
const token = jwt.sign(
|
const token = jwt.sign(
|
||||||
{
|
{
|
||||||
u: verify.userId.raw,
|
u: verify.userId,
|
||||||
r: verify.refreshCount,
|
r: verify.refreshCount,
|
||||||
},
|
},
|
||||||
process.env.JWT_REFRESH_KEY as string,
|
process.env.JWT_REFRESH_KEY as string,
|
||||||
@@ -54,13 +54,13 @@ async function token(request: UnwrappedRequest): Promise<Response> {
|
|||||||
r: string;
|
r: string;
|
||||||
} = jwt.verify(refreshCookie, process.env.JWT_REFRESH_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))) {
|
if (!(await orm.users.verifyRefreshCount(UserId.fromHash(refreshToken.u), refreshToken.r))) {
|
||||||
const response = new UnauthorizedResponse('Invalid refresh token');
|
const response = new UnauthorizedResponse('Invalid refresh token');
|
||||||
response.headers.set('Clear-Site-Data', '"cookies","cache","storage","executionContexts"');
|
response.headers.set('Clear-Site-Data', '"cookies","cache","storage","executionContexts"');
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
const claims: Claims | null = await orm.claims.getByUserId(refreshToken.u);
|
const claims: Claims | null = await orm.claims.getByUserId(UserId.fromHash(refreshToken.u));
|
||||||
|
|
||||||
const token = jwt.sign({ ...claims }, process.env.JWT_SECRET_KEY as string, {
|
const token = jwt.sign({ ...claims }, process.env.JWT_SECRET_KEY as string, {
|
||||||
expiresIn: process.env.JWT_LIFESPAN as any,
|
expiresIn: process.env.JWT_LIFESPAN as any,
|
||||||
|
|||||||
@@ -3,30 +3,41 @@ import { ClaimDefinition } from '../utilities/claimDefinitions';
|
|||||||
import { UserId } from '../utilities/secureIds';
|
import { UserId } from '../utilities/secureIds';
|
||||||
|
|
||||||
export class Claims extends ClaimDefinition {
|
export class Claims extends ClaimDefinition {
|
||||||
userId?: UserId;
|
userId: UserId;
|
||||||
claims: string[] = [];
|
claims: string[] = [];
|
||||||
|
|
||||||
constructor(raw?: { userId?: string; claims?: string[] }) {
|
constructor(raw?: { userId?: string | UserId; claims?: string[] }) {
|
||||||
super();
|
super();
|
||||||
this.userId = raw?.userId ? UserId.fromHash(raw.userId) : undefined;
|
if(raw?.userId instanceof UserId) {
|
||||||
|
this.userId = raw.userId
|
||||||
|
} else {
|
||||||
|
this.userId = UserId.fromHash(raw?.userId ?? '');
|
||||||
|
}
|
||||||
this.claims = raw?.claims ?? [];
|
this.claims = raw?.claims ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static test(guardClaim: string, userClaims?: Claims): Boolean {
|
test(...guardClaims: string[]): Boolean {
|
||||||
return userClaims === undefined || userClaims.claims.some((x) => x === guardClaim);
|
return Claims.test(this, ...guardClaims);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static test(userClaims?: Claims, ...guardClaims: string[]): Boolean {
|
||||||
|
return (
|
||||||
|
userClaims === undefined ||
|
||||||
|
userClaims.claims.some((x: string): boolean => guardClaims.some((y: string): boolean => x === y))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ClaimsOrm {
|
export class ClaimsOrm {
|
||||||
async getByUserId(userId: string): Promise<Claims> {
|
async getByUserId(userId: UserId): Promise<Claims> {
|
||||||
const dbResults: any[] = await sql`SELECT c.name
|
const dbResults: any[] = await sql`SELECT c.name
|
||||||
from user_claims as uc
|
from user_claims as uc
|
||||||
JOIN claims as c on uc.claim_id = c.id
|
JOIN claims as c on uc.claim_id = c.id
|
||||||
where uc.user_id = ${userId};`;
|
where uc.user_id = ${userId.raw};`;
|
||||||
const claims = new Claims();
|
return new Claims({
|
||||||
claims.userId = UserId.fromID(userId);
|
userId: userId,
|
||||||
claims.claims = dbResults.map((x) => x.name);
|
claims: dbResults.map((x) => x.name),
|
||||||
return claims;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDefaultClaims(): Promise<number[]> {
|
async getDefaultClaims(): Promise<number[]> {
|
||||||
|
|||||||
@@ -4,16 +4,18 @@ import { first } from 'lodash';
|
|||||||
import { NotFoundError, UnauthorizedError } from '../utilities/errors';
|
import { NotFoundError, UnauthorizedError } from '../utilities/errors';
|
||||||
import { UpdateCollectionRequest } from '../utilities/requestModels';
|
import { UpdateCollectionRequest } from '../utilities/requestModels';
|
||||||
import { Game } from './games';
|
import { Game } from './games';
|
||||||
import { CollectionId, GameId } from '../utilities/secureIds';
|
import { CollectionId, GameId, UserId } from '../utilities/secureIds';
|
||||||
|
|
||||||
export class Collection {
|
export class Collection {
|
||||||
id: CollectionId;
|
id: CollectionId;
|
||||||
name: string;
|
name: string;
|
||||||
|
ownerId: UserId;
|
||||||
games: Game[];
|
games: Game[];
|
||||||
|
|
||||||
constructor(input: { id: CollectionId; name: string; games?: Game[] }) {
|
constructor(input: { id: CollectionId; name: string; ownerId:UserId; games?: Game[] }) {
|
||||||
this.id = input.id;
|
this.id = input.id;
|
||||||
this.name = input?.name;
|
this.name = input?.name;
|
||||||
|
this.ownerId = input.ownerId;
|
||||||
this.games = input.games ?? [];
|
this.games = input.games ?? [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,13 +40,13 @@ export class CollectionsOrm {
|
|||||||
LEFT JOIN collection_games cg ON cg.collection_id = c.id
|
LEFT JOIN collection_games cg ON cg.collection_id = c.id
|
||||||
LEFT JOIN games g ON g.id = cg.game_id
|
LEFT JOIN games g ON g.id = cg.game_id
|
||||||
WHERE c.id = ${id.raw}`;
|
WHERE c.id = ${id.raw}`;
|
||||||
|
if (
|
||||||
if (!(Claims.test(Claims.ADMIN, claims) || Claims.test(Claims.COLLECTIONS.UNOWNED.READ, claims))) {
|
claims &&
|
||||||
throw new UnauthorizedError();
|
!(
|
||||||
} else if (
|
claims.test(Claims.ADMIN, Claims.COLLECTIONS.UNOWNED.READ) ||
|
||||||
Claims.test(Claims.COLLECTIONS.OWNED.READ, claims) &&
|
(Claims.test(claims, Claims.COLLECTIONS.OWNED.READ) &&
|
||||||
claims?.userId &&
|
dbResult?.[0]?.user_id === claims?.userId?.raw)
|
||||||
dbResult?.[0]?.user_id !== claims.userId.raw
|
)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
@@ -56,6 +58,7 @@ export class CollectionsOrm {
|
|||||||
return new Collection({
|
return new Collection({
|
||||||
id: CollectionId.fromID(dbResult[0].collection_id),
|
id: CollectionId.fromID(dbResult[0].collection_id),
|
||||||
name: dbResult[0].collection_name,
|
name: dbResult[0].collection_name,
|
||||||
|
ownerId: UserId.fromID(dbResult[0].user_id),
|
||||||
games: dbResult
|
games: dbResult
|
||||||
.filter((x: { game_id: string; game_name: string }) => x.game_id)
|
.filter((x: { game_id: string; game_name: string }) => x.game_id)
|
||||||
.map(
|
.map(
|
||||||
@@ -69,25 +72,27 @@ export class CollectionsOrm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async list(claims?: Claims): Promise<Collection[]> {
|
async list(claims?: Claims): Promise<Collection[]> {
|
||||||
if (!claims || Claims.test(Claims.ADMIN, claims)) {
|
if (!claims || claims?.test(Claims.ADMIN)) {
|
||||||
return (await sql`SELECT * FROM collections`).map(
|
return (await sql`SELECT * FROM collections`).map(
|
||||||
(x: { id: string; name: string }) =>
|
(x: { id: string; name: string, user_id: string }) =>
|
||||||
new Collection({
|
new Collection({
|
||||||
id: CollectionId.fromID(x.id),
|
id: CollectionId.fromID(x.id),
|
||||||
name: x.name,
|
name: x.name,
|
||||||
|
ownerId: UserId.fromID(x.user_id)
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Claims.test(Claims.COLLECTIONS.OWNED.LIST, claims)) {
|
if (!claims.test(Claims.COLLECTIONS.OWNED.LIST)) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (await sql`SELECT * FROM collections WHERE user_id=${claims.userId?.raw}`).map(
|
return (await sql`SELECT * FROM collections WHERE user_id=${claims.userId?.raw}`).map(
|
||||||
(x: { id: string; name: string }) =>
|
(x: { id: string; name: string; user_id: string }) =>
|
||||||
new Collection({
|
new Collection({
|
||||||
id: CollectionId.fromID(x.id),
|
id: CollectionId.fromID(x.id),
|
||||||
name: x.name,
|
name: x.name,
|
||||||
|
ownerId: UserId.fromID(x.user_id)
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -95,15 +100,17 @@ export class CollectionsOrm {
|
|||||||
async update(id: CollectionId, patch: UpdateCollectionRequest, claims?: Claims): Promise<Collection> {
|
async update(id: CollectionId, patch: UpdateCollectionRequest, claims?: Claims): Promise<Collection> {
|
||||||
const collection = await this.get(id);
|
const collection = await this.get(id);
|
||||||
|
|
||||||
if (!(Claims.test(Claims.ADMIN, claims) || Claims.test(Claims.COLLECTIONS.UNOWNED.UPDATE, claims))) {
|
if (
|
||||||
throw new UnauthorizedError();
|
claims &&
|
||||||
} else if (
|
!(
|
||||||
Claims.test(Claims.COLLECTIONS.OWNED.UPDATE, claims) &&
|
claims.test(Claims.ADMIN, Claims.COLLECTIONS.UNOWNED.UPDATE) ||
|
||||||
claims?.userId &&
|
(Claims.test(claims, Claims.COLLECTIONS.OWNED.UPDATE) &&
|
||||||
collection.id !== claims.userId
|
collection.ownerId === claims?.userId)
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
collection.name = patch.name ?? collection.name;
|
collection.name = patch.name ?? collection.name;
|
||||||
|
|
||||||
await sql`UPDATE collections SET name=${collection.name} WHERE id=${id.raw}`;
|
await sql`UPDATE collections SET name=${collection.name} WHERE id=${id.raw}`;
|
||||||
@@ -113,12 +120,14 @@ export class CollectionsOrm {
|
|||||||
|
|
||||||
async drop(id: CollectionId, claims?: Claims): Promise<void> {
|
async drop(id: CollectionId, claims?: Claims): Promise<void> {
|
||||||
const collection = await this.get(id);
|
const collection = await this.get(id);
|
||||||
if (!(Claims.test(Claims.ADMIN, claims) || Claims.test(Claims.COLLECTIONS.UNOWNED.DELETE, claims))) {
|
|
||||||
throw new UnauthorizedError();
|
if (
|
||||||
} else if (
|
claims &&
|
||||||
Claims.test(Claims.COLLECTIONS.OWNED.DELETE, claims) &&
|
!(
|
||||||
claims?.userId &&
|
claims.test(Claims.ADMIN, Claims.COLLECTIONS.UNOWNED.DELETE) ||
|
||||||
collection.id !== claims.userId
|
(Claims.test(claims, Claims.COLLECTIONS.OWNED.DELETE) &&
|
||||||
|
collection.ownerId === claims?.userId)
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
@@ -130,12 +139,14 @@ export class CollectionsOrm {
|
|||||||
|
|
||||||
async addGame(id: CollectionId, gameId: GameId, claims: Claims): Promise<void> {
|
async addGame(id: CollectionId, gameId: GameId, claims: Claims): Promise<void> {
|
||||||
const collection = await this.get(id);
|
const collection = await this.get(id);
|
||||||
if (!(Claims.test(Claims.ADMIN, claims) || Claims.test(Claims.COLLECTIONS.UNOWNED.GAME.ADD, claims))) {
|
|
||||||
throw new UnauthorizedError();
|
if (
|
||||||
} else if (
|
claims &&
|
||||||
Claims.test(Claims.COLLECTIONS.OWNED.GAME.ADD, claims) &&
|
!(
|
||||||
claims?.userId &&
|
claims.test(Claims.ADMIN) ||
|
||||||
collection.id !== claims.userId
|
(Claims.test(claims, Claims.COLLECTIONS.OWNED.GAME.ADD) &&
|
||||||
|
collection.ownerId === claims?.userId)
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
@@ -147,12 +158,14 @@ export class CollectionsOrm {
|
|||||||
}
|
}
|
||||||
async removeGame(id: CollectionId, gameId: GameId, claims: Claims): Promise<void> {
|
async removeGame(id: CollectionId, gameId: GameId, claims: Claims): Promise<void> {
|
||||||
const collection = await this.get(id);
|
const collection = await this.get(id);
|
||||||
if (!(Claims.test(Claims.ADMIN, claims) || Claims.test(Claims.COLLECTIONS.UNOWNED.GAME.REMOVE, claims))) {
|
|
||||||
throw new UnauthorizedError();
|
if (
|
||||||
} else if (
|
claims &&
|
||||||
Claims.test(Claims.COLLECTIONS.OWNED.GAME.REMOVE, claims) &&
|
!(
|
||||||
claims?.userId &&
|
claims.test(Claims.ADMIN) ||
|
||||||
collection.id !== claims.userId
|
(Claims.test(claims, Claims.COLLECTIONS.OWNED.GAME.REMOVE) &&
|
||||||
|
collection.ownerId === claims?.userId)
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export class Game {
|
|||||||
export class GamesOrm {
|
export class GamesOrm {
|
||||||
async create(model: CreateGameRequest, claims?: Claims): Promise<Game> {
|
async create(model: CreateGameRequest, claims?: Claims): Promise<Game> {
|
||||||
await sql`INSERT INTO games (name, image_path, bgg_id)
|
await sql`INSERT INTO games (name, image_path, bgg_id)
|
||||||
VALUES (${model.name}, ${Claims.test(Claims.GAMES.MANAGE_IMAGES, claims) ? model.imagePath : null}, ${model.bggId})`;
|
VALUES (${model.name}, ${claims?.test(Claims.GAMES.MANAGE_IMAGES) ? model.imagePath : null}, ${model.bggId})`;
|
||||||
const newGameId: string = (first(await sql`SELECT lastval();`) as any)?.lastval as string;
|
const newGameId: string = (first(await sql`SELECT lastval();`) as any)?.lastval as string;
|
||||||
|
|
||||||
return await this.get(GameId.fromID(newGameId));
|
return await this.get(GameId.fromID(newGameId));
|
||||||
@@ -54,7 +54,7 @@ export class GamesOrm {
|
|||||||
gameToUpdate.name = patch.name ?? gameToUpdate.name;
|
gameToUpdate.name = patch.name ?? gameToUpdate.name;
|
||||||
gameToUpdate.bggId = patch.bggId ?? gameToUpdate.bggId;
|
gameToUpdate.bggId = patch.bggId ?? gameToUpdate.bggId;
|
||||||
|
|
||||||
if (Claims.test(Claims.GAMES.MANAGE_IMAGES, claims)) {
|
if (claims?.test(Claims.GAMES.MANAGE_IMAGES)) {
|
||||||
gameToUpdate.imagePath = patch.imagePath ?? gameToUpdate.imagePath;
|
gameToUpdate.imagePath = patch.imagePath ?? gameToUpdate.imagePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export class InvitesOrm {
|
|||||||
},
|
},
|
||||||
claims?: Claims,
|
claims?: Claims,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!Claims.test(Claims.ADMIN, claims)) {
|
if (!claims?.test(Claims.ADMIN)) {
|
||||||
const userInviteCount = (
|
const userInviteCount = (
|
||||||
first(
|
first(
|
||||||
await sql`SELECT COUNT(*) AS count
|
await sql`SELECT COUNT(*) AS count
|
||||||
|
|||||||
@@ -116,10 +116,11 @@ export class MatchOrm {
|
|||||||
WHERE m.id = ${id.raw}`;
|
WHERE m.id = ${id.raw}`;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
claims &&
|
||||||
!(
|
!(
|
||||||
Claims.test(Claims.ADMIN, claims) ||
|
claims.test(Claims.ADMIN) ||
|
||||||
(Claims.test(Claims.MATCHES.OWNED.READ, claims) && dbResult?.[0]?.owner_id === claims?.userId?.raw) ||
|
(claims.test(Claims.MATCHES.OWNED.READ) && dbResult?.[0]?.owner_id === claims?.userId?.raw) ||
|
||||||
(Claims.test(Claims.MATCHES.PARTICIPANT.READ, claims) &&
|
(claims.test(Claims.MATCHES.PARTICIPANT.READ) &&
|
||||||
dbResult?.some((x: any) => x.player_id === claims?.userId?.raw))
|
dbResult?.some((x: any) => x.player_id === claims?.userId?.raw))
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@@ -157,10 +158,8 @@ export class MatchOrm {
|
|||||||
async drop(id: MatchId, claims?: Claims): Promise<void> {
|
async drop(id: MatchId, claims?: Claims): Promise<void> {
|
||||||
const match = await this.get(id);
|
const match = await this.get(id);
|
||||||
if (
|
if (
|
||||||
!(
|
claims &&
|
||||||
Claims.test(Claims.ADMIN, claims) ||
|
!(claims.test(Claims.ADMIN) || (claims.test(Claims.MATCHES.OWNED.DELETE) && match.owner === claims?.userId))
|
||||||
(Claims.test(Claims.MATCHES.OWNED.DELETE, claims) && match.owner === claims?.userId)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,14 +38,15 @@ export class PlayersOrm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async get(id: PlayerId, 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))) {
|
if(claims) {
|
||||||
throw new UnauthorizedError();
|
|
||||||
} else if (Claims.test(Claims.PLAYERS.SELF.READ, claims) && claims?.userId) {
|
|
||||||
const user = await orm.users.get(claims.userId);
|
const user = await orm.users.get(claims.userId);
|
||||||
if (id.raw !== user.playerId.raw) {
|
if(!(claims.test(Claims.ADMIN, Claims.PLAYERS.OTHER.READ) ||
|
||||||
|
claims.test(Claims.PLAYERS.SELF.READ) && id === user.playerId)) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dbResult: any = first(
|
const dbResult: any = first(
|
||||||
await sql`SELECT *
|
await sql`SELECT *
|
||||||
FROM players
|
FROM players
|
||||||
@@ -67,7 +68,7 @@ export class PlayersOrm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async list(claims?: Claims): Promise<Player[]> {
|
async list(claims?: Claims): Promise<Player[]> {
|
||||||
if (!claims || Claims.test(Claims.ADMIN, claims)) {
|
if (!claims || claims.test(Claims.ADMIN)) {
|
||||||
return (await sql`SELECT * FROM players`).map(
|
return (await sql`SELECT * FROM players`).map(
|
||||||
(x: { id: string; name: string; elo: string; is_rating_locked: boolean; can_be_multiple: boolean }) =>
|
(x: { id: string; name: string; elo: string; is_rating_locked: boolean; can_be_multiple: boolean }) =>
|
||||||
new Player({
|
new Player({
|
||||||
@@ -80,7 +81,7 @@ export class PlayersOrm {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Claims.test(Claims.PLAYERS.OTHER.READ, claims)) {
|
if (!claims.test(Claims.PLAYERS.OTHER.READ)) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,11 +110,11 @@ export class PlayersOrm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async update(id: PlayerId, 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))) {
|
if(claims) {
|
||||||
throw new UnauthorizedError();
|
|
||||||
} else if (Claims.test(Claims.PLAYERS.SELF.UPDATE, claims) && claims?.userId) {
|
|
||||||
const user = await orm.users.get(claims.userId);
|
const user = await orm.users.get(claims.userId);
|
||||||
if (id.raw !== user.playerId.raw) {
|
if(!(claims.test(Claims.ADMIN, Claims.PLAYERS.OTHER.UPDATE) ||
|
||||||
|
(claims.test(Claims.PLAYERS.SELF.UPDATE) && id === user.playerId)
|
||||||
|
)) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,11 +134,11 @@ export class PlayersOrm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async drop(id: PlayerId, 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))) {
|
if(claims) {
|
||||||
throw new UnauthorizedError();
|
|
||||||
} else if (Claims.test(Claims.PLAYERS.SELF.DELETE, claims) && claims?.userId) {
|
|
||||||
const user = await orm.users.get(claims.userId);
|
const user = await orm.users.get(claims.userId);
|
||||||
if (id.raw !== user.playerId.raw) {
|
if(!(claims.test(Claims.ADMIN, Claims.PLAYERS.OTHER.DELETE) ||
|
||||||
|
(claims.test(Claims.PLAYERS.SELF.DELETE) && id === user.playerId)
|
||||||
|
)) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,10 +60,10 @@ export class UsersOrm {
|
|||||||
|
|
||||||
async get(id: UserId, claims?: Claims): Promise<User> {
|
async get(id: UserId, claims?: Claims): Promise<User> {
|
||||||
if (
|
if (
|
||||||
|
claims &&
|
||||||
!(
|
!(
|
||||||
Claims.test(Claims.ADMIN, claims) ||
|
claims.test(Claims.ADMIN, Claims.USERS.OTHER.READ) ||
|
||||||
Claims.test(Claims.USERS.OTHER.READ, claims) ||
|
(claims.test(Claims.USERS.SELF.READ) && id === claims?.userId)
|
||||||
(Claims.test(Claims.USERS.SELF.READ, claims) && id === claims?.userId)
|
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
@@ -91,43 +91,35 @@ export class UsersOrm {
|
|||||||
|
|
||||||
async update(id: UserId, patch: UpdateUserRequest, claims?: Claims): Promise<User> {
|
async update(id: UserId, patch: UpdateUserRequest, claims?: Claims): Promise<User> {
|
||||||
if (
|
if (
|
||||||
|
claims &&
|
||||||
!(
|
!(
|
||||||
Claims.test(Claims.ADMIN, claims) ||
|
claims.test(Claims.ADMIN, Claims.USERS.OTHER.UPDATE) ||
|
||||||
Claims.test(Claims.USERS.OTHER.UPDATE, claims) ||
|
(claims.test(Claims.USERS.SELF.UPDATE) && id === claims?.userId)
|
||||||
(Claims.test(Claims.USERS.SELF.UPDATE, claims) && id === claims?.userId)
|
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
const userToUpdate = await this.get(id);
|
const userToUpdate = await this.get(id);
|
||||||
if (Claims.test(Claims.ADMIN, claims)) {
|
if (!claims || claims.test(Claims.ADMIN)) {
|
||||||
userToUpdate.isActive = patch.isActive ?? userToUpdate.isActive;
|
userToUpdate.isActive = patch.isActive ?? userToUpdate.isActive;
|
||||||
userToUpdate.isAdmin = patch.isAdmin ?? userToUpdate.isAdmin;
|
userToUpdate.isAdmin = patch.isAdmin ?? userToUpdate.isAdmin;
|
||||||
}
|
}
|
||||||
|
|
||||||
await sql.transaction(async (tx) => {
|
await sql`UPDATE users
|
||||||
await tx`UPDATE users
|
|
||||||
SET is_active=${userToUpdate.isActive},
|
SET is_active=${userToUpdate.isActive},
|
||||||
is_admin=${userToUpdate.isAdmin}
|
is_admin=${userToUpdate.isAdmin}
|
||||||
WHERE id = ${id.raw}`;
|
WHERE id = ${id.raw}`;
|
||||||
|
|
||||||
if (id === claims?.userId && patch.password) {
|
|
||||||
const passwordHash = await argon2.hash(patch.password);
|
|
||||||
await tx`UPDATE users
|
|
||||||
SET pass_hash = ${passwordHash}`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return await this.get(id);
|
return await this.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async drop(id: UserId, claims?: Claims): Promise<void> {
|
async drop(id: UserId, claims?: Claims): Promise<void> {
|
||||||
if (
|
if (
|
||||||
|
claims &&
|
||||||
!(
|
!(
|
||||||
Claims.test(Claims.ADMIN, claims) ||
|
claims.test(Claims.ADMIN, Claims.USERS.OTHER.DELETE) ||
|
||||||
Claims.test(Claims.USERS.OTHER.DELETE, claims) ||
|
(claims.test(Claims.USERS.SELF.DELETE) && id === claims?.userId)
|
||||||
(Claims.test(Claims.USERS.SELF.DELETE, claims) && id === claims?.userId)
|
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
@@ -180,8 +172,8 @@ export class UsersOrm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async changePassword(id: UserId, oldPassword: string | null, newPassword: string, claims?: Claims): Promise<void> {
|
async changePassword(id: UserId, oldPassword: string | null, newPassword: string, claims?: Claims): Promise<void> {
|
||||||
const isAdmin = Claims.test(Claims.ADMIN, claims);
|
const isAdmin = claims?.test(Claims.ADMIN) ?? true;
|
||||||
if (!(isAdmin || (Claims.test(Claims.USERS.SELF.UPDATE, claims) && id === claims?.userId))) {
|
if (!(isAdmin || (claims?.test(Claims.USERS.SELF.UPDATE) && id === claims?.userId))) {
|
||||||
throw new UnauthorizedError();
|
throw new UnauthorizedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,14 +11,12 @@ export default {
|
|||||||
add: {
|
add: {
|
||||||
POST: guard(collections.addGame, [
|
POST: guard(collections.addGame, [
|
||||||
Claims.ADMIN,
|
Claims.ADMIN,
|
||||||
Claims.COLLECTIONS.UNOWNED.GAME.ADD,
|
|
||||||
Claims.COLLECTIONS.OWNED.GAME.ADD,
|
Claims.COLLECTIONS.OWNED.GAME.ADD,
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
remove: {
|
remove: {
|
||||||
POST: guard(collections.removeGame, [
|
POST: guard(collections.removeGame, [
|
||||||
Claims.ADMIN,
|
Claims.ADMIN,
|
||||||
Claims.COLLECTIONS.UNOWNED.GAME.REMOVE,
|
|
||||||
Claims.COLLECTIONS.OWNED.GAME.REMOVE,
|
Claims.COLLECTIONS.OWNED.GAME.REMOVE,
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -29,49 +29,40 @@ export class ClaimDefinition {
|
|||||||
};
|
};
|
||||||
public static readonly CIRCLES = {
|
public static readonly CIRCLES = {
|
||||||
PUBLIC: {
|
PUBLIC: {
|
||||||
|
READ: 'CIRCLES_OWNED_READ',
|
||||||
CREATE: 'CIRCLES_PUBLIC_CREATE',
|
CREATE: 'CIRCLES_PUBLIC_CREATE',
|
||||||
JOIN: 'CIRCLES_PUBLIC_JOIN',
|
JOIN: 'CIRCLES_PUBLIC_JOIN',
|
||||||
USERS: {
|
|
||||||
ADD: 'CIRCLES_PUBLIC_USER_ADD',
|
|
||||||
LIST: 'CIRCLES_PUBLIC_USER_LIST',
|
|
||||||
INVITE: 'CIRCLES_PUBLIC_USER_INVITE',
|
|
||||||
},
|
|
||||||
COMMENTS: {
|
COMMENTS: {
|
||||||
ADD: 'CIRCLES_PUBLIC_COMMENTS_ADD',
|
ADD: 'CIRCLES_PUBLIC_COMMENTS_ADD',
|
||||||
DELETE: 'CIRCLES_PUBLIC_COMMENTS_DELETE',
|
},
|
||||||
|
USERS: {
|
||||||
|
INVITE: 'CIRCLES_OWNED_USER_INVITE',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
PRIVATE: {
|
PRIVATE: {
|
||||||
|
READ: 'CIRCLES_PRIVATE_READ',
|
||||||
|
READ_IF_MEMBER: 'CIRCLES_PRIVATE_READ_IF_MEMBER',
|
||||||
CREATE: 'CIRCLES_PRIVATE_CREATE',
|
CREATE: 'CIRCLES_PRIVATE_CREATE',
|
||||||
USERS: {
|
COMMENTS: {
|
||||||
INVITE: 'CIRCLES_PRIVATE_USER_INVITE',
|
ADD: 'CIRCLES_PRIVATE_COMMENTS_ADD',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
OWNED: {
|
OWNED: {
|
||||||
READ: 'CIRCLES_OWNED_READ',
|
READ: 'CIRCLES_OWNED_READ',
|
||||||
UPDATE: 'CIRCLES_OWNED_UPDATE',
|
UPDATE: 'CIRCLES_OWNED_UPDATE',
|
||||||
DELETE: 'CIRCLES_OWNED_DELETE',
|
DELETE: 'CIRCLES_OWNED_DELETE',
|
||||||
USERS: {
|
PLAYERS: {
|
||||||
ADD: 'CIRCLES_OWNED_USER_ADD',
|
ADD: 'CIRCLES_OWNED_USER_ADD',
|
||||||
LIST: 'CIRCLES_OWNED_USER_LIST',
|
LIST: 'CIRCLES_OWNED_USER_LIST',
|
||||||
USERS: {
|
},
|
||||||
INVITE: 'CIRCLES_OWNED_USER_INVITE',
|
USERS: {
|
||||||
},
|
INVITE: 'CIRCLES_OWNED_USER_INVITE',
|
||||||
},
|
},
|
||||||
COMMENTS: {
|
COMMENTS: {
|
||||||
ADD: 'CIRCLES_OWNED_COMMENTS_ADD',
|
ADD: 'CIRCLES_OWNED_COMMENTS_ADD',
|
||||||
DELETE: 'CIRCLES_OWNED_COMMENTS_DELETE',
|
DELETE: 'CIRCLES_OWNED_COMMENTS_DELETE',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
UNOWNED: {
|
|
||||||
READ: 'CIRCLES_UNOWNED_READ',
|
|
||||||
UPDATE: 'CIRCLES_UNOWNED_UPDATE',
|
|
||||||
DELETE: 'CIRCLES_UNOWNED_DELETE',
|
|
||||||
COMMENTS: {
|
|
||||||
ADD: 'CIRCLES_UNOWNED_COMMENTS_ADD',
|
|
||||||
DELETE: 'CIRCLES_UNOWNED_COMMENTS_DELETE',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
public static readonly GAMES = {
|
public static readonly GAMES = {
|
||||||
CREATE: 'GAMES_CREATE',
|
CREATE: 'GAMES_CREATE',
|
||||||
@@ -120,10 +111,6 @@ export class ClaimDefinition {
|
|||||||
READ: 'COLLECTIONS_UNOWNED_READ',
|
READ: 'COLLECTIONS_UNOWNED_READ',
|
||||||
UPDATE: 'COLLECTIONS_UNOWNED_UPDATE',
|
UPDATE: 'COLLECTIONS_UNOWNED_UPDATE',
|
||||||
DELETE: 'COLLECTIONS_UNOWNED_DELETE',
|
DELETE: 'COLLECTIONS_UNOWNED_DELETE',
|
||||||
GAME: {
|
|
||||||
ADD: 'COLLECTIONS_UNOWNED_GAME_ADD',
|
|
||||||
REMOVE: 'COLLECTIONS_UNOWNED_GAME_REMOVE',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
public static readonly COMMENTS = {
|
public static readonly COMMENTS = {
|
||||||
|
|||||||
@@ -23,13 +23,18 @@ export function guard(
|
|||||||
const authHeader: string | null =
|
const authHeader: string | null =
|
||||||
(request.headers.get('Authorization')?.replace(/^Bearer /, '') as string) ?? null;
|
(request.headers.get('Authorization')?.replace(/^Bearer /, '') as string) ?? null;
|
||||||
try {
|
try {
|
||||||
const userClaims: Claims = new Claims(jwt.verify(authHeader as string, process.env.JWT_SECRET_KEY as string) as any);
|
const userClaims: Claims = new Claims(
|
||||||
if (!userClaims.claims.some((x: string): boolean => guardedClaims.includes(x))) {
|
jwt.verify(authHeader as string, process.env.JWT_SECRET_KEY as string) as any,
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
!userClaims.userId.raw ||
|
||||||
|
!userClaims.claims.some((x: string): boolean => guardedClaims.includes(x))
|
||||||
|
) {
|
||||||
return new UnauthorizedResponse('Unauthorized');
|
return new UnauthorizedResponse('Unauthorized');
|
||||||
}
|
}
|
||||||
return method(await unwrap(request, userClaims));
|
return method(await unwrap(request, userClaims));
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
if (error instanceof TokenExpiredError) {
|
if (error instanceof TokenExpiredError) {
|
||||||
return new UnauthorizedResponse(error.message);
|
return new UnauthorizedResponse(error.message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ export interface CreateUserRequest {
|
|||||||
export interface UpdateUserRequest {
|
export interface UpdateUserRequest {
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
isAdmin?: boolean;
|
isAdmin?: boolean;
|
||||||
password?: string;
|
|
||||||
}
|
}
|
||||||
export interface InviteUserRequest {
|
export interface InviteUserRequest {
|
||||||
email: string;
|
email: string;
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class SecureId {
|
|||||||
constructor(id: { public?: string; secure?: string }, hashScheme?: HashIds) {
|
constructor(id: { public?: string; secure?: string }, hashScheme?: HashIds) {
|
||||||
this.#hashScheme = hashScheme ?? (this.constructor as any).hashScheme;
|
this.#hashScheme = hashScheme ?? (this.constructor as any).hashScheme;
|
||||||
|
|
||||||
if (id.public) {
|
if (id.public !== undefined) {
|
||||||
this.value = id.public;
|
this.value = id.public;
|
||||||
} else if (id.secure) {
|
} else if (id.secure) {
|
||||||
this.raw = id.secure;
|
this.raw = id.secure;
|
||||||
@@ -134,3 +134,15 @@ export class MatchId extends SecureId {
|
|||||||
return super.fromID(id, MatchId);
|
return super.fromID(id, MatchId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CircleId extends SecureId {
|
||||||
|
protected static override hashPrefix: string = 'CircleId';
|
||||||
|
|
||||||
|
public static fromHash(hash: string): CircleId {
|
||||||
|
return super.fromHash(hash, CircleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromID(id: string): CircleId {
|
||||||
|
return super.fromID(id, CircleId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user