Skip to content
This repository was archived by the owner on Apr 19, 2023. It is now read-only.

Commit 7861a14

Browse files
✨ Support for team username
1 parent 92619d2 commit 7861a14

File tree

6 files changed

+86
-56
lines changed

6 files changed

+86
-56
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "staart",
3-
"version": "1.0.10",
3+
"version": "1.0.11",
44
"main": "index.js",
55
"repository": "git@github.com:AnandChowdhary/staart.git",
66
"author": "Anand Chowdhary <mail@anandchowdhary.com>",

src/controllers/organization.ts

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import { MembershipRole } from "../interfaces/enum";
3636
import { CREATED } from "http-status-codes";
3737
import asyncHandler from "express-async-handler";
3838
import { inviteMemberToOrganization } from "../rest/membership";
39-
import { joiValidate } from "../helpers/utils";
39+
import { joiValidate, organizationUsernameToId } from "../helpers/utils";
4040
import Joi from "@hapi/joi";
4141

4242
@Controller("organizations")
@@ -71,15 +71,15 @@ export class OrganizationController {
7171

7272
@Get(":id")
7373
async get(req: Request, res: Response) {
74-
const id = req.params.id;
74+
const id = await organizationUsernameToId(req.params.id);
7575
joiValidate({ id: Joi.number().required() }, { id });
7676
const organization = await getOrganizationForUser(res.locals.token.id, id);
7777
res.json(organization);
7878
}
7979

8080
@Patch(":id")
8181
async patch(req: Request, res: Response) {
82-
const id = req.params.id;
82+
const id = await organizationUsernameToId(req.params.id);
8383
joiValidate({ id: Joi.number().required() }, { id });
8484
await updateOrganizationForUser(
8585
res.locals.token.id,
@@ -92,7 +92,7 @@ export class OrganizationController {
9292

9393
@Delete(":id")
9494
async delete(req: Request, res: Response) {
95-
const organizationId = req.params.id;
95+
const organizationId = await organizationUsernameToId(req.params.id);
9696
const userId = res.locals.token.id;
9797
joiValidate(
9898
{ organizationId: Joi.number().required() },
@@ -104,7 +104,7 @@ export class OrganizationController {
104104

105105
@Get(":id/billing")
106106
async getBilling(req: Request, res: Response) {
107-
const organizationId = req.params.id;
107+
const organizationId = await organizationUsernameToId(req.params.id);
108108
joiValidate(
109109
{ organizationId: Joi.number().required() },
110110
{ organizationId }
@@ -116,7 +116,7 @@ export class OrganizationController {
116116

117117
@Patch(":id/billing")
118118
async patchBilling(req: Request, res: Response) {
119-
const organizationId = req.params.id;
119+
const organizationId = await organizationUsernameToId(req.params.id);
120120
joiValidate(
121121
{ organizationId: Joi.number().required() },
122122
{ organizationId }
@@ -132,7 +132,7 @@ export class OrganizationController {
132132

133133
@Get(":id/invoices")
134134
async getInvoices(req: Request, res: Response) {
135-
const organizationId = req.params.id;
135+
const organizationId = await organizationUsernameToId(req.params.id);
136136
joiValidate(
137137
{ organizationId: Joi.number().required() },
138138
{ organizationId }
@@ -159,7 +159,7 @@ export class OrganizationController {
159159

160160
@Get(":id/invoices/:invoiceId")
161161
async getInvoice(req: Request, res: Response) {
162-
const organizationId = req.params.id;
162+
const organizationId = await organizationUsernameToId(req.params.id);
163163
const invoiceId = req.params.invoiceId;
164164
joiValidate(
165165
{
@@ -179,7 +179,7 @@ export class OrganizationController {
179179

180180
@Get(":id/sources")
181181
async getSources(req: Request, res: Response) {
182-
const organizationId = req.params.id;
182+
const organizationId = await organizationUsernameToId(req.params.id);
183183
joiValidate(
184184
{ organizationId: Joi.number().required() },
185185
{ organizationId }
@@ -203,7 +203,7 @@ export class OrganizationController {
203203

204204
@Get(":id/sources/:sourceId")
205205
async getSource(req: Request, res: Response) {
206-
const organizationId = req.params.id;
206+
const organizationId = await organizationUsernameToId(req.params.id);
207207
const sourceId = req.params.sourceId;
208208
joiValidate(
209209
{
@@ -223,7 +223,7 @@ export class OrganizationController {
223223

224224
@Get(":id/subscriptions")
225225
async getSubscriptions(req: Request, res: Response) {
226-
const organizationId = req.params.id;
226+
const organizationId = await organizationUsernameToId(req.params.id);
227227
joiValidate(
228228
{ organizationId: Joi.number().required() },
229229
{ organizationId }
@@ -250,7 +250,7 @@ export class OrganizationController {
250250

251251
@Get(":id/subscriptions/:subscriptionId")
252252
async getSubscription(req: Request, res: Response) {
253-
const organizationId = req.params.id;
253+
const organizationId = await organizationUsernameToId(req.params.id);
254254
const subscriptionId = req.params.subscriptionId;
255255
joiValidate(
256256
{
@@ -270,7 +270,7 @@ export class OrganizationController {
270270

271271
@Patch(":id/subscriptions/:subscriptionId")
272272
async patchSubscription(req: Request, res: Response) {
273-
const organizationId = req.params.id;
273+
const organizationId = await organizationUsernameToId(req.params.id);
274274
const subscriptionId = req.params.subscriptionId;
275275
const data = req.body;
276276
joiValidate(
@@ -301,7 +301,7 @@ export class OrganizationController {
301301

302302
@Put(":id/subscriptions")
303303
async putSubscriptions(req: Request, res: Response) {
304-
const organizationId = req.params.id;
304+
const organizationId = await organizationUsernameToId(req.params.id);
305305
joiValidate(
306306
{ organizationId: Joi.number().required() },
307307
{ organizationId }
@@ -328,7 +328,7 @@ export class OrganizationController {
328328
@Get(":id/pricing/:product")
329329
async getPlans(req: Request, res: Response) {
330330
const product = req.params.product;
331-
const organizationId = req.params.id;
331+
const organizationId = await organizationUsernameToId(req.params.id);
332332
joiValidate(
333333
{
334334
organizationId: Joi.number().required(),
@@ -347,7 +347,7 @@ export class OrganizationController {
347347

348348
@Put(":id/sources")
349349
async putSources(req: Request, res: Response) {
350-
const organizationId = req.params.id;
350+
const organizationId = await organizationUsernameToId(req.params.id);
351351
joiValidate(
352352
{ organizationId: Joi.number().required() },
353353
{ organizationId }
@@ -366,7 +366,7 @@ export class OrganizationController {
366366
@Delete(":id/sources/:sourceId")
367367
async deleteSource(req: Request, res: Response) {
368368
const sourceId = req.params.sourceId;
369-
const organizationId = req.params.id;
369+
const organizationId = await organizationUsernameToId(req.params.id);
370370
joiValidate(
371371
{
372372
organizationId: Joi.number().required(),
@@ -386,7 +386,7 @@ export class OrganizationController {
386386
@Patch(":id/sources/:sourceId")
387387
async patchSource(req: Request, res: Response) {
388388
const sourceId = req.params.sourceId;
389-
const organizationId = req.params.id;
389+
const organizationId = await organizationUsernameToId(req.params.id);
390390
joiValidate(
391391
{
392392
organizationId: Joi.number().required(),
@@ -406,7 +406,7 @@ export class OrganizationController {
406406

407407
@Get(":id/data")
408408
async getData(req: Request, res: Response) {
409-
const organizationId = req.params.id;
409+
const organizationId = await organizationUsernameToId(req.params.id);
410410
joiValidate(
411411
{ organizationId: Joi.number().required() },
412412
{ organizationId }
@@ -418,7 +418,7 @@ export class OrganizationController {
418418

419419
@Get(":id/events")
420420
async getEvents(req: Request, res: Response) {
421-
const organizationId = req.params.id;
421+
const organizationId = await organizationUsernameToId(req.params.id);
422422
joiValidate(
423423
{ organizationId: Joi.number().required() },
424424
{ organizationId }
@@ -433,7 +433,7 @@ export class OrganizationController {
433433

434434
@Get(":id/memberships")
435435
async getMemberships(req: Request, res: Response) {
436-
const organizationId = req.params.id;
436+
const organizationId = await organizationUsernameToId(req.params.id);
437437
joiValidate(
438438
{ organizationId: Joi.number().required() },
439439
{ organizationId }
@@ -449,7 +449,7 @@ export class OrganizationController {
449449

450450
@Put(":id/memberships")
451451
async putMemberships(req: Request, res: Response) {
452-
const organizationId = req.params.id;
452+
const organizationId = await organizationUsernameToId(req.params.id);
453453
const newMemberName = req.body.name;
454454
const newMemberEmail = req.body.email;
455455
const role = req.body.role;

src/crud/organization.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Organization } from "../interfaces/tables/organization";
88
import { capitalizeFirstAndLastLetter, dateToDateTime } from "../helpers/utils";
99
import { KeyValue } from "../interfaces/general";
1010
import { cachedQuery, deleteItemFromCache } from "../helpers/cache";
11-
import { CacheCategories } from "../interfaces/enum";
11+
import { CacheCategories, ErrorCode } from "../interfaces/enum";
1212

1313
/*
1414
* Create a new organization for a user
@@ -29,14 +29,32 @@ export const createOrganization = async (organization: Organization) => {
2929
* Get the details of a specific organization
3030
*/
3131
export const getOrganization = async (id: number) => {
32-
return (<Organization[]>(
32+
const org = (<Organization[]>(
3333
await cachedQuery(
3434
CacheCategories.ORGANIZATION,
3535
id,
3636
`SELECT * FROM organizations WHERE id = ? LIMIT 1`,
3737
[id]
3838
)
3939
))[0];
40+
if (org) return org;
41+
throw new Error(ErrorCode.ORGANIZATION_NOT_FOUND);
42+
};
43+
44+
/*
45+
* Get the details of a specific organization
46+
*/
47+
export const getOrganizationIdFromUsername = async (username: string) => {
48+
const org = (<Organization[]>(
49+
await cachedQuery(
50+
CacheCategories.ORGANIZATION_USERNAME,
51+
username,
52+
`SELECT id FROM organizations WHERE username = ? LIMIT 1`,
53+
[username]
54+
)
55+
))[0];
56+
if (org && org.id) return org.id;
57+
throw new Error(ErrorCode.ORGANIZATION_NOT_FOUND);
4058
};
4159

4260
/*

src/helpers/authorization.ts

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
UserRole,
77
MembershipRole
88
} from "../interfaces/enum";
9-
import { getUser } from "../crud/user";
9+
import { getUser, getApiKey } from "../crud/user";
1010
import { getUserMemberships, getMembership } from "../crud/membership";
1111
import { getOrganization } from "../crud/organization";
1212
import { Membership } from "../interfaces/tables/memberships";
@@ -179,42 +179,44 @@ export const can = async (
179179
targetType: "user" | "organization" | "membership" | "api-key" | "general",
180180
target?: User | Organization | Membership | ApiKey | number
181181
) => {
182-
let userObject;
183-
if (typeof user === "number") {
184-
userObject = await getUser(user);
182+
let userObject: User;
183+
if (typeof target === "object") {
184+
userObject = target as User;
185185
} else {
186-
userObject = user;
187-
}
188-
let targetObject;
189-
if (typeof target === "string") {
190-
let newTarget = parseInt(target);
191-
if (!isNaN(newTarget)) target = newTarget;
192-
}
193-
if (typeof target == "number") {
194-
if (targetType === "user") {
195-
targetObject = await getUser(target);
196-
} else if (targetType === "organization") {
197-
targetObject = await getOrganization(target);
198-
} else {
199-
targetObject = await getMembership(target);
200-
}
201-
} else {
202-
targetObject = target;
186+
userObject = await getUser(user as number);
203187
}
204188
if (!userObject.id) throw new Error(ErrorCode.USER_NOT_FOUND);
189+
190+
let targetObject: User | Organization | Membership | ApiKey;
205191
if (targetType === "user") {
206-
return await canUserUser(userObject, action, <User>targetObject);
192+
if (typeof target === "string" || typeof target === "number")
193+
targetObject = await getUser(target);
194+
else targetObject = target as User;
195+
return await canUserUser(userObject, action, targetObject as User);
207196
} else if (targetType === "organization") {
208-
return await canUserOrganization(userObject, action, <Organization>(
209-
targetObject
210-
));
197+
if (typeof target === "string" || typeof target === "number")
198+
targetObject = await getOrganization(target);
199+
else targetObject = target as Organization;
200+
return await canUserOrganization(
201+
userObject,
202+
action,
203+
targetObject as Organization
204+
);
211205
} else if (targetType === "membership") {
212-
return await canUserMembership(userObject, action, <Membership>(
213-
targetObject
214-
));
206+
if (typeof target === "string" || typeof target === "number")
207+
targetObject = await getMembership(target);
208+
else targetObject = target as Membership;
209+
return await canUserMembership(
210+
userObject,
211+
action,
212+
targetObject as Membership
213+
);
215214
} else if (targetType === "api-key") {
216-
return await canUserApiKey(userObject, action, <ApiKey>targetObject);
217-
} else {
218-
return await canUserGeneral(userObject, action);
215+
if (typeof target === "string" || typeof target === "number")
216+
targetObject = await getApiKey(target.toString());
217+
else targetObject = target as ApiKey;
218+
return await canUserApiKey(userObject, action, targetObject as ApiKey);
219219
}
220+
221+
return await canUserGeneral(userObject, action);
220222
};

src/helpers/utils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import anonymize from "ip-anonymize";
22
import { User } from "../interfaces/tables/user";
33
import Joi from "@hapi/joi";
4+
import { getOrganizationIdFromUsername } from "../crud/organization";
45

56
/**
67
* Capitalize each first letter in a string
@@ -52,6 +53,14 @@ export const deleteSensitiveInfoUser = (user: User) => {
5253
export const anonymizeIpAddress = (ipAddress: string) =>
5354
anonymize(ipAddress) || ipAddress;
5455

56+
export const organizationUsernameToId = async (id: string) => {
57+
if (isNaN(Number(id))) {
58+
return await getOrganizationIdFromUsername(id);
59+
} else {
60+
return parseInt(id);
61+
}
62+
};
63+
5564
/**
5665
* MySQL columns which are booleans
5766
*/

src/interfaces/enum.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export enum CacheCategories {
104104
ORGANIZATION_MEMBERSHIPS = "memberships",
105105
MEMBERSHIP = "membership",
106106
ORGANIZATION = "organization",
107+
ORGANIZATION_USERNAME = "organization-username",
107108
APPROVE_LOCATIONS = "approved-locations",
108109
APPROVE_LOCATION = "approved-location",
109110
IP_LOOKUP = "ip-lookup",

0 commit comments

Comments
 (0)