Skip to content

Commit

Permalink
merge with main
Browse files Browse the repository at this point in the history
  • Loading branch information
armintalaie committed Mar 9, 2024
2 parents 575d247 + 3f758ff commit ed36b07
Show file tree
Hide file tree
Showing 19 changed files with 616 additions and 389 deletions.
388 changes: 191 additions & 197 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 6 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
},
"scripts": {
"clean": "rm -rf dist",
"build": " sh ./scripts/build",
"test:api": " sh ./scripts/serve",
"build": " sh ./scripts/build",
"test:api": " sh ./scripts/serve",
"watch": "tsc -w",
"cdk:synth": "cdk synth",
"lint": "eslint .",
Expand All @@ -23,7 +23,6 @@
"test:src": "jest --verbose ./src",
"start:local:api": "sam local start-api -t ./cdk.out/team-stack.template.json --port 3003 ",
"invoke:lambda": "sam local invoke -t ./cdk.out/$npm_config_stack.template.json -e $npm_config_event ${npm_config_warm-containers=LAZY}",

"start:local:lambdas": "sam local start-lambda -t ./cdk.out/$npm_config_stack.template.json ${npm_config_port:3000} ${npm_config_warm-containers=LAZY}",
"db:up": "esbuild ./resources/**/*.ts --entry-names=[dir]/[name]/index --bundle --platform=node --target=node16.14 --outdir=./dist/migrations && node ./dist/migrations/setup/index.js"
},
Expand All @@ -49,12 +48,12 @@
"@types/aws-sdk": "^2.7.0",
"@types/jest": "^29.5.2",
"@types/node": "^18.14.6",
"@typescript-eslint/eslint-plugin": "^5.59.9",
"@typescript-eslint/parser": "^5.59.9",
"@typescript-eslint/eslint-plugin": "^6.20.0",
"@typescript-eslint/parser": "^6.20.0",
"aws-cdk": "2.70.0",
"babel-jest": "^29.5.0",
"esbuild": "^0.17.19",
"eslint": "^8.42.0",
"eslint": "^8.56.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"jest": "^29.5.0",
Expand All @@ -63,4 +62,4 @@
"ts-node": "^10.9.1",
"typescript": "~4.9.5"
}
}
}
8 changes: 6 additions & 2 deletions resources/database/migrations/1703270939.sql
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ CREATE TABLE IF NOT EXISTS specialization (
label VARCHAR(100) UNIQUE NOT NULL
);

CREATE TABLE IF NOT EXISTS person_role (
person_id INT,
role_id INT,
PRIMARY KEY (person_id, role_id)
);

-- Create the role table
CREATE TABLE IF NOT EXISTS role (
id INT AUTO_INCREMENT PRIMARY KEY,
Expand All @@ -59,7 +65,6 @@ CREATE TABLE IF NOT EXISTS person (
email VARCHAR(255),
member_since TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
account_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
person_role_id INT,
first_name VARCHAR(255) NOT NULL,
pref_name VARCHAR(255),
last_name VARCHAR(255) NOT NULL,
Expand All @@ -75,7 +80,6 @@ CREATE TABLE IF NOT EXISTS person (
github_link VARCHAR(255),
website_link VARCHAR(255),
resume_link VARCHAR(255),
KEY person_role_id_idx (person_role_id),
KEY pronouns_id_idx (pronouns_id),
KEY gender_id_idx (gender_id),
KEY ethicity_id_idx (ethnicity_id),
Expand Down
4 changes: 2 additions & 2 deletions resources/database/migrations/1703270982.sql
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ VALUES
('read:profile:restricted', 'Read certain data of profiles'),
('read:profile:personal', 'Read all data to your own profile'),
('write:admin', 'Admin full write access'),
('write:profile', 'Write all data of profiles'),
('write:profile:all', 'Write all data of profiles'),
('update:admin', 'Admin full update access'),
('update:profile:all', 'Update all data of profiles'),
('update:profile:personal', 'Update all data to your own profile'),
Expand All @@ -172,4 +172,4 @@ VALUES
('read:profile:personal', 'Member'),
('read:profile:restricted', 'Member'),
('update:profile:personal', 'Member'),
('delete:profile:personal', 'Member');
('delete:profile:personal', 'Member');
2 changes: 1 addition & 1 deletion resources/database/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import mysql, { Connection } from 'mysql2';
console.log('Connected to PlanetScale!')
dotenv.config();

const DATABASE_NAME = 'cosmic-dev';
const DATABASE_NAME = 'userbase';
const MIGRATION_PATH = './resources/database/migrations';

const setUpDatabase = async (
Expand Down
35 changes: 26 additions & 9 deletions src/roles/addUserRole.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,40 @@
import { getDatabase } from '../util/db';
import { LambdaBuilder } from '../util/middleware/middleware';
import { BadRequestError, SuccessResponse } from '../util/middleware/response';
import { InputValidator } from '../util/middleware/inputValidator';
import { APIGatewayEvent } from 'aws-lambda';
import { Authorizer } from '../util/middleware/authorizer';
import { InputValidator } from '../util/middleware/inputValidator';
import { LambdaBuilder, LambdaInput } from '../util/middleware/middleware';
import { BadRequestError, SuccessResponse } from '../util/middleware/response';
import {
ACCESS_SCOPES,
ScopeController,
} from '../util/middleware/scopeHandler';

const db = getDatabase();

// Only valid for user with Admin role
const validScopes = [ACCESS_SCOPES.ADMIN_WRITE];

export const handler = new LambdaBuilder(addUserRoleRequest)
.use(new InputValidator())
.use(new Authorizer())
.use(new Authorizer(db))
.use(new ScopeController(db))
.build();

async function addUserRoleRequest(event: APIGatewayEvent) {
async function addUserRoleRequest(event: LambdaInput) {
if (!event.pathParameters) {
throw new BadRequestError('Event path parameters missing');
}
if (!event.pathParameters.id || !event.pathParameters.roleId) {
throw new BadRequestError('User id or role id missing');
}
const { id, roleId } = event.pathParameters;
await addRole(id, roleId);

ScopeController.verifyScopes(event.userScopes, validScopes);

await addRole(parseInt(id), parseInt(roleId));
return new SuccessResponse({ message: 'Role added successfully' });
}

export const addRole = async (userId: string, roleId: string) => {
export const addRole = async (userId: number, roleId: number) => {
const verifyRole = await db
.selectFrom('role')
.select('id')
Expand All @@ -30,6 +47,6 @@ export const addRole = async (userId: string, roleId: string) => {

await db
.insertInto('person_role')
.values({ user_id: userId, role_id: roleId })
.values({ person_id: userId, role_id: roleId })
.execute();
};
35 changes: 27 additions & 8 deletions src/roles/createRole.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,50 @@
import { getDatabase, NewRole } from '../util/db';
import {LambdaBuilder, LambdaInput} from '../util/middleware/middleware';
import { SuccessResponse } from '../util/middleware/response';
import { Authorizer } from '../util/middleware/authorizer';
import { InputValidator } from '../util/middleware/inputValidator';
import { LambdaBuilder, LambdaInput } from '../util/middleware/middleware';
import { BadRequestError, SuccessResponse } from '../util/middleware/response';
import {
ACCESS_SCOPES,
ScopeController,
} from '../util/middleware/scopeHandler';
import { getRoles, refreshCache } from './roles';
import { Authorizer } from '../util/middleware/authorizer';
import {ScopeController} from "../util/middleware/scopeHandler";

const db = getDatabase();

// Only valid for Admin role
const validScopes = [ACCESS_SCOPES.ADMIN_WRITE];
export const handler = new LambdaBuilder(createRoleRequest)
.use(new InputValidator())
.use(new Authorizer())
.use(new Authorizer(db))
.use(new ScopeController(db))
.build();

async function createRoleRequest(event: LambdaInput) {
ScopeController.verifyScopes(event.userScopes, ['admin:write'])
if (!event.userScopes) {
throw new BadRequestError('Event userScopes missing');
}
if (!event.body) {
throw new BadRequestError('Event body missing');
}

ScopeController.verifyScopes(event.userScopes, validScopes);

const { label } = JSON.parse(event.body);
await createRole({ label });
await refreshCache(db);
return new SuccessResponse(await getRoles(db));
}

export const createRole = async (newRole: NewRole) => {
const { id } = await db
const result = await db
.insertInto('role')
.values(newRole)
.returning('id')
.executeTakeFirst();
return id;

if (result === undefined) {
throw new BadRequestError('Role not created');
}

return result.id;
};
30 changes: 22 additions & 8 deletions src/roles/deleteRole.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,40 @@
import { getDatabase } from '../util/db';
import {LambdaBuilder, LambdaInput} from '../util/middleware/middleware';
import { SuccessResponse } from '../util/middleware/response';
import { Authorizer } from '../util/middleware/authorizer';
import { InputValidator } from '../util/middleware/inputValidator';
import { LambdaBuilder, LambdaInput } from '../util/middleware/middleware';
import { BadRequestError, SuccessResponse } from '../util/middleware/response';
import {
ACCESS_SCOPES,
ScopeController,
} from '../util/middleware/scopeHandler';
import { getRoles, refreshCache } from './roles';
import { Authorizer } from '../util/middleware/authorizer';
import {ScopeController} from "../util/middleware/scopeHandler";

const db = getDatabase();

// Only valid for Admin role
const validScopes = [ACCESS_SCOPES.ADMIN_WRITE];
export const handler = new LambdaBuilder(deleteRoleRequest)
.use(new InputValidator())
.use(new Authorizer())
.use(new Authorizer(db))
.use(new ScopeController(db))
.build();

async function deleteRoleRequest(event: LambdaInput) {
ScopeController.verifyScopes(event.userScopes, ['admin:write'])
const { id } = event.pathParameters;
if (!event.pathParameters) {
throw new BadRequestError('Event path parameters missing');
}
if (!event.pathParameters.id) {
throw new BadRequestError('ID is undefined');
}
const id = parseInt(event.pathParameters.id);

ScopeController.verifyScopes(event.userScopes, validScopes);

await deleteRole(id);
await refreshCache(db);
return new SuccessResponse(await getRoles(db));
}

export const deleteRole = async (id: string) => {
export const deleteRole = async (id: number) => {
await db.deleteFrom('role').where('id', '=', id).execute();
};
43 changes: 30 additions & 13 deletions src/roles/deleteUserRole.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,53 @@
import { getDatabase } from '../util/db';
import { LambdaBuilder } from '../util/middleware/middleware';
import { BadRequestError, SuccessResponse } from '../util/middleware/response';
import { InputValidator } from '../util/middleware/inputValidator';
import { APIGatewayEvent } from 'aws-lambda';
import { Authorizer } from '../util/middleware/authorizer';
import { InputValidator } from '../util/middleware/inputValidator';
import { LambdaBuilder, LambdaInput } from '../util/middleware/middleware';
import { BadRequestError, SuccessResponse } from '../util/middleware/response';
import {
ACCESS_SCOPES,
ScopeController,
} from '../util/middleware/scopeHandler';

const db = getDatabase();

// Only valid for user with Admin role
const validScopes = [ACCESS_SCOPES.ADMIN_WRITE];
export const handler = new LambdaBuilder(deleteUserRoleRequest)
.use(new InputValidator())
.use(new Authorizer())
.use(new Authorizer(db))
.use(new ScopeController(db))
.build();

async function deleteUserRoleRequest(event: APIGatewayEvent) {
const { id, roleId } = JSON.parse(event.body);
await deleteUserRole(id, roleId);
async function deleteUserRoleRequest(event: LambdaInput) {
if (!event.pathParameters) {
throw new BadRequestError('Event pathParameters missing');
}
if (!event.pathParameters.id || !event.pathParameters.roleId) {
throw new BadRequestError('User id or role id missing');
}

const { id, roleId } = event.pathParameters;
ScopeController.verifyScopes(event.userScopes, validScopes);
await deleteUserRole(parseInt(id), parseInt(roleId));
return new SuccessResponse({ message: 'Role deleted successfully' });
}

export const deleteUserRole = async (userId: string, roleId: string) => {
export const deleteUserRole = async (userId: number, roleId: number) => {
const verifyRole = await db
.selectFrom('person_role')
.select(['user_id', 'role_id'])
.where('user_id', '=', userId)
.select(['person_id', 'role_id'])
.where('person_id', '=', userId)
.where('role_id', '=', roleId)
.executeTakeFirst();
if (!verifyRole) {
throw new BadRequestError(`role id: ${roleId} does not exist for user id: ${userId}`);
throw new BadRequestError(
`role id: ${roleId} does not exist for user id: ${userId}`
);
}

await db
.deleteFrom('person_role')
.where('user_id', '=', userId)
.where('person_id', '=', userId)
.where('role_id', '=', roleId)
.execute();
};
43 changes: 26 additions & 17 deletions src/roles/getUserRoles.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,47 @@
import {Database, getDatabase} from '../util/db';
import { LambdaBuilder } from '../util/middleware/middleware';
import { SuccessResponse } from '../util/middleware/response';
import { InputValidator } from '../util/middleware/inputValidator';
import { APIGatewayEvent } from 'aws-lambda';
import { Kysely } from 'kysely';
import { Database, getDatabase } from '../util/db';
import { InputValidator } from '../util/middleware/inputValidator';
import { LambdaBuilder } from '../util/middleware/middleware';
import { BadRequestError, SuccessResponse } from '../util/middleware/response';
import { Authorizer } from '../util/middleware/authorizer';
import {Kysely} from "kysely";

const db = getDatabase();
export const handler = new LambdaBuilder(getUserRoleRequest)
.use(new InputValidator())
.use(new Authorizer())
.use(new Authorizer(db))
.build();

async function getUserRoleRequest(event: APIGatewayEvent) {
const {id} = event.pathParameters;
const roles = await getUserRoles(id);
const userRoles: {id: string, label: string, scopes: string[]}[] = [];
roles.map(role => userRoles.push({id: role.id, label: role.label, scopes: []}));
if (!event.pathParameters) {
throw new BadRequestError('Event path parameters missing');
}
const { id } = event.pathParameters;
const roles = await getUserRoles(Number(id));
const userRoles: { id: number; label: string; scopes: string[] }[] = [];
roles.map((role) =>
userRoles.push({ id: role.id, label: role.label, scopes: [] })
);
for (const role of userRoles) {
role.scopes = await attachScopes(db, role.label);
}
return new SuccessResponse(userRoles);
}

export const getUserRoles = async (userId: string) => {
export const getUserRoles = async (userId: number) => {
return await db
.selectFrom('person_role')
.innerJoin('role', 'role.id', 'person_role.role_id')
.selectFrom('role')
.innerJoin('person_role', 'role.id', 'person_role.role_id')
.select(['role.id', 'role.label'])
.where('user_id', '=', userId)
.where('person_role.person_id', '=', userId)
.execute();
};

export const attachScopes = async (db: Kysely<Database>, role: string) => {
const scopes = await db.selectFrom('scope_role').select(['scope_label']).where('role_label', '=', role).execute();
return scopes.map(scope => scope.scope_label);
}
const scopes = await db
.selectFrom('scope_role')
.select(['scope_label'])
.where('role_label', '=', role)
.execute();
return scopes.map((scope) => scope.scope_label);
};
Loading

0 comments on commit ed36b07

Please sign in to comment.