First authz check
This commit is contained in:
parent
773439d2e0
commit
e667f39852
3 changed files with 60 additions and 11 deletions
0
access.ts
Normal file
0
access.ts
Normal file
54
db/mod.ts
54
db/mod.ts
|
@ -38,6 +38,36 @@ export function initDatabaseConnectionPool({ url }: PostgresConfig) {
|
|||
testDbConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a certain SQL predicate fetches a row that indeed exists.
|
||||
*
|
||||
* sqlSnippet should assume it comes after a 'select * from'. For example: '"user" where id = 1'.
|
||||
*/
|
||||
async function rowExists(
|
||||
sqlSnippet: string,
|
||||
args: unknown[],
|
||||
): Promise<boolean> {
|
||||
const result = await queryArray<[boolean]>(
|
||||
`select exists(select 1 from ${sqlSnippet});`,
|
||||
args,
|
||||
);
|
||||
if (result && result.rows.length > 0) {
|
||||
log.info("rowExists result:", result.rows);
|
||||
return !!(result.rows[0][0]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function isUserInTeam(
|
||||
userId: string,
|
||||
teamId: string,
|
||||
): Promise<boolean> {
|
||||
return await rowExists("team_user where user_id = $1 and team_id = $2", [
|
||||
userId,
|
||||
teamId,
|
||||
]);
|
||||
}
|
||||
|
||||
export async function testDbConnection(): Promise<boolean> {
|
||||
try {
|
||||
await dbOp((conn) => conn.queryObject("select 1"));
|
||||
|
@ -111,17 +141,20 @@ export async function dbOp<T>(
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Example: queryObject('select * from "user" where id = $1', [userId])
|
||||
*/
|
||||
export async function queryObject<T>(
|
||||
sql: string,
|
||||
args?: QueryArguments,
|
||||
connection?: PoolClient | Transaction,
|
||||
): Promise<QueryObjectResult<T> | null> {
|
||||
console.debug(`queryObject: ${sql}`);
|
||||
if (!connection) {
|
||||
return await dbOp(async (connection) => {
|
||||
return await queryObject(sql, args, connection);
|
||||
});
|
||||
} else {
|
||||
log.debug(`queryObject: ${sql}`);
|
||||
const result = await connection.queryObject<T>({
|
||||
camelcase: true,
|
||||
text: sql.trim(),
|
||||
|
@ -132,17 +165,20 @@ export async function queryObject<T>(
|
|||
}
|
||||
}
|
||||
|
||||
export async function queryArray<T extends []>(
|
||||
/**
|
||||
* Example: queryArray('select * from "user" where id = $1', [userId])
|
||||
*/
|
||||
export async function queryArray<T extends unknown[]>(
|
||||
sql: string,
|
||||
args?: QueryArguments,
|
||||
connection?: PoolClient,
|
||||
): Promise<QueryArrayResult<T> | null> {
|
||||
console.debug(`queryArray: ${sql}`);
|
||||
if (!connection) {
|
||||
return await dbOp(async (connection) => {
|
||||
return await queryArray<T>(sql, args, connection);
|
||||
});
|
||||
} else {
|
||||
log.debug(`queryArray: ${sql}`);
|
||||
const result = await connection.queryArray<T>({
|
||||
text: sql.trim(),
|
||||
args,
|
||||
|
@ -210,7 +246,7 @@ export async function createTeam(
|
|||
},
|
||||
transaction?: Transaction,
|
||||
): Promise<Team> {
|
||||
console.debug("createTeam tx:", transaction);
|
||||
log.debug("createTeam tx:", transaction);
|
||||
if (!transaction) {
|
||||
return await wrapWithTransaction<Team>(
|
||||
"createTeam",
|
||||
|
@ -234,7 +270,7 @@ export async function createTeam(
|
|||
}
|
||||
return team;
|
||||
} catch (e) {
|
||||
console.error("Error creating team:", e);
|
||||
log.error("Error creating team:", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
@ -253,7 +289,7 @@ export async function wrapWithTransaction<T>(
|
|||
);
|
||||
try {
|
||||
await transaction.begin();
|
||||
console.debug(
|
||||
log.debug(
|
||||
`started ${transactionName} tx with options ${
|
||||
JSON.stringify(transactionOptions)
|
||||
}:`,
|
||||
|
@ -264,11 +300,11 @@ export async function wrapWithTransaction<T>(
|
|||
return result;
|
||||
} catch (e) {
|
||||
await transaction.rollback();
|
||||
console.error("Failed to complete transaction:", e);
|
||||
log.error("Failed to complete transaction:", e);
|
||||
throw e;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to create transaction");
|
||||
log.error("Failed to create transaction");
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
@ -304,7 +340,7 @@ export async function createUser(
|
|||
|
||||
return user;
|
||||
} catch (e) {
|
||||
console.error("Error creating user:", e);
|
||||
log.error("Error creating user:", e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,31 @@
|
|||
import { Handlers, PageProps } from "$fresh/server.ts";
|
||||
import { getTeam, getTeamUsers } from "@/db/mod.ts";
|
||||
import { getTeam, getTeamUsers, isUserInTeam } from "@/db/mod.ts";
|
||||
import { type Team, type User } from "@/types.ts";
|
||||
import { type ContextState } from "@/types.ts";
|
||||
|
||||
interface TeamPageProps {
|
||||
team: Team;
|
||||
users: User[];
|
||||
}
|
||||
|
||||
export const handler: Handlers<TeamPageProps> = {
|
||||
export const handler: Handlers<TeamPageProps, ContextState> = {
|
||||
async GET(request, context) {
|
||||
if (!context.state.user?.id) {
|
||||
// unauthenticated requests may not view teams
|
||||
return await context.renderNotFound();
|
||||
}
|
||||
// TODO: implement this with row-level security?
|
||||
// TODO: do I just use supabase at this point?
|
||||
// TODO: only allow logged-in users to view teams (and most resources!)
|
||||
// TODO: only allow users that are a member of a team to view them
|
||||
// NOTE: maybe teams can be public...?
|
||||
const { id } = context.params;
|
||||
|
||||
if (!await isUserInTeam(context.state.user?.id, id)) {
|
||||
// users that are not a member of a team may not view it
|
||||
return await context.renderNotFound();
|
||||
}
|
||||
|
||||
console.debug({ request, context });
|
||||
try {
|
||||
const team = await getTeam({ id });
|
||||
|
|
Loading…
Reference in a new issue