Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 37 additions & 18 deletions lib/services/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type User = {
username: string;
displayName: string;
role: string;
subClaim: string;
subClaim: string | null;
};

class AuthServiceError extends Error {
Expand All @@ -38,7 +38,7 @@ type AccessToken = {
role: string;
name: string;
};
function generateAccessToken(user: User) {
function generateAccessToken(user: User): string {
return jwt.sign({
exp: Math.floor(Date.now()/1000) + (60*20), // In seconds, 20m expiration
iss: 'mustachebash',
Expand All @@ -49,7 +49,7 @@ function generateAccessToken(user: User) {
config.jwt.secret);
}

export function validateAccessToken(accessToken: string) {
export function validateAccessToken(accessToken: string): AccessToken {
const tokenPayload = jwt.verify(accessToken, config.jwt.secret, {issuer: 'mustachebash'});

if(
Expand All @@ -66,7 +66,7 @@ type RefreshToken = {
jti: string;
sub: string;
};
function generateRefreshToken(user: User, jti: string) {
function generateRefreshToken(user: User, jti: string): string {
return jwt.sign({
exp: Math.floor(Date.now()/1000) + (60*60*24*30), // In seconds, 30d expiration
iss: 'mustachebash',
Expand All @@ -77,7 +77,7 @@ function generateRefreshToken(user: User, jti: string) {
config.jwt.secret);
}

function validateRefreshToken(refreshToken: string) {
function validateRefreshToken(refreshToken: string): RefreshToken {
const tokenPayload = jwt.verify(refreshToken, config.jwt.secret, {issuer: 'mustachebash', audience: 'mustachebash-refresh'});

if(
Expand All @@ -91,7 +91,12 @@ function validateRefreshToken(refreshToken: string) {
return tokenPayload as RefreshToken;
}

export async function authenticateGoogleUser(token: string, {userAgent, ip}: {userAgent: string, ip: string}) {
type AuthResult = {
accessToken: string;
refreshToken: string;
};

export async function authenticateGoogleUser(token: string, {userAgent, ip}: {userAgent: string, ip: string}): Promise<AuthResult> {
if(!token) throw new AuthServiceError('Missing token', 'UNAUTHORIZED');

let payload: TokenPayload | undefined, googleUserId: string | null;
Expand Down Expand Up @@ -192,15 +197,19 @@ export async function authenticateGoogleUser(token: string, {userAgent, ip}: {us
return {accessToken, refreshToken};
}

export async function refreshAccessToken(refreshToken: string, {userAgent, ip}: {userAgent: string, ip: string}) {
type RefreshTokenData = {
userId: string;
};

export async function refreshAccessToken(refreshToken: string, {userAgent, ip}: {userAgent: string, ip: string}): Promise<string> {
let sub: string, jti: string;
try {
({ sub, jti } = validateRefreshToken(refreshToken));
} catch(e) {
throw new AuthServiceError('Invalid refresh token', 'UNAUTHORIZED');
}

let user;
let user: User | undefined;
try {
[user] = await sql<User[]>`
SELECT id, display_name, role, sub_claim
Expand All @@ -211,9 +220,9 @@ export async function refreshAccessToken(refreshToken: string, {userAgent, ip}:
throw new AuthServiceError('Failed to query for user', 'DB_ERROR', e);
}

let refreshTokenData;
let refreshTokenData: RefreshTokenData | undefined;
try {
[refreshTokenData] = await sql`
[refreshTokenData] = await sql<RefreshTokenData[]>`
SELECT user_id
FROM refresh_tokens
WHERE id = ${jti}
Expand All @@ -240,7 +249,7 @@ export async function refreshAccessToken(refreshToken: string, {userAgent, ip}:
return generateAccessToken(user);
}

export function checkScope(userRole: string, scopeRequired: string) {
export function checkScope(userRole: string, scopeRequired: string): boolean {
const roles = [
'root',
'god',
Expand All @@ -252,13 +261,23 @@ export function checkScope(userRole: string, scopeRequired: string) {

const userLevel = roles.indexOf(userRole);

return ~userLevel && userLevel <= roles.indexOf(scopeRequired);
return Boolean(~userLevel && userLevel <= roles.indexOf(scopeRequired));
}

export async function getUsers() {
let users;
type UserPublic = {
id: string;
username: string;
displayName: string;
role: string;
status: string;
created: Date;
updated: Date;
};

export async function getUsers(): Promise<UserPublic[]> {
let users: UserPublic[];
try {
users = await sql`
users = await sql<UserPublic[]>`
SELECT id, username, display_name, role, status, created, updated
FROM users
`;
Expand All @@ -269,10 +288,10 @@ export async function getUsers() {
return users;
}

export async function getUser(id: string) {
let user;
export async function getUser(id: string): Promise<UserPublic> {
let user: UserPublic | undefined;
try {
[user] = await sql`
[user] = await sql<UserPublic[]>`
SELECT id, username, display_name, role, status, created, updated
FROM users
WHERE id = ${id}
Expand Down
55 changes: 40 additions & 15 deletions lib/services/customers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class CustomerServiceError extends Error {
code: string;
context?: unknown;

constructor(message = 'An unknown error occured', code = 'UNKNOWN', context) {
constructor(message = 'An unknown error occured', code = 'UNKNOWN', context?: unknown) {
super(message);

this.name = this.constructor.name;
Expand All @@ -32,8 +32,25 @@ const customerColumns = [
'meta'
];


export async function createCustomer({ firstName, lastName, email, meta }) {
export type Customer = {
id: string;
email: string;
firstName: string;
lastName: string;
created: Date;
updated: Date;
updatedBy: string | null;
meta: Record<string, unknown>;
};

type CreateCustomerInput = {
firstName: string;
lastName: string;
email: string;
meta?: Record<string, unknown>;
};

export async function createCustomer({ firstName, lastName, email, meta }: CreateCustomerInput): Promise<Customer> {
if(!firstName || !lastName || !email) throw new CustomerServiceError('Missing customer data', 'INVALID');
if(!/.+@.+\..{2,}/.test(email)) throw new CustomerServiceError('Invalid email', 'INVALID');

Expand All @@ -48,20 +65,20 @@ export async function createCustomer({ firstName, lastName, email, meta }) {
};

try {
const [createdCustomer] = (await sql`
const [createdCustomer] = await sql<Customer[]>`
INSERT INTO customers ${sql(customer)}
RETURNING ${sql(customerColumns)}
`);
`;

return createdCustomer;
} catch(e) {
throw new CustomerServiceError('Could not create customer', 'UNKNOWN', e);
}
}

export async function getCustomers() {
export async function getCustomers(): Promise<Customer[]> {
try {
const customers = await sql`
const customers = await sql<Customer[]>`
SELECT ${sql(customerColumns)}
FROM customers
`;
Expand All @@ -72,14 +89,14 @@ export async function getCustomers() {
}
}

export async function getCustomer(id) {
let customer;
export async function getCustomer(id: string): Promise<Customer> {
let customer: Customer | undefined;
try {
[customer] = (await sql`
[customer] = await sql<Customer[]>`
SELECT ${sql(customerColumns)}
FROM customers
WHERE id = ${id}
`);
`;
} catch(e) {
throw new CustomerServiceError('Could not query customers', 'UNKNOWN', e);
}
Expand All @@ -89,7 +106,15 @@ export async function getCustomer(id) {
return customer;
}

export async function updateCustomer(id, updates) {
type UpdateCustomerInput = {
firstName?: string;
lastName?: string;
email?: string;
meta?: Record<string, unknown>;
updatedBy?: string;
};

export async function updateCustomer(id: string, updates: UpdateCustomerInput): Promise<Customer> {
for(const u in updates) {
// Update whitelist
if(![
Expand All @@ -103,14 +128,14 @@ export async function updateCustomer(id, updates) {

if(Object.keys(updates).length === 1 && updates.updatedBy) throw new CustomerServiceError('Invalid customer data', 'INVALID');

let customer;
let customer: Customer | undefined;
try {
[customer] = (await sql`
[customer] = await sql<Customer[]>`
UPDATE customers
SET ${sql(updates)}, updated = now()
WHERE id = ${id}
RETURNING ${sql(customerColumns)}
`);
`;
} catch(e) {
throw new CustomerServiceError('Could not update customer', 'UNKNOWN', e);
}
Expand Down
13 changes: 10 additions & 3 deletions lib/services/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ const mailchimp = new MailChimpClient(config.mailchimp.apiKey),
* @param {Array} [tags=[] string }] tags to apply to member
* @return {Promise}
*/
export async function upsertEmailSubscriber(listId, { email, firstName, lastName, tags = [] }) {
type UpsertEmailSubscriberInput = {
email: string;
firstName: string;
lastName: string;
tags?: string[];
};

export async function upsertEmailSubscriber(listId: string, { email, firstName, lastName, tags = [] }: UpsertEmailSubscriberInput): Promise<void> {
const memberHash = md5(email.toLowerCase());

try {
Expand All @@ -50,7 +57,7 @@ export async function upsertEmailSubscriber(listId, { email, firstName, lastName
}
}

export function sendReceipt(guestFirstName, guestLastName, guestEmail, confirmation, orderId, orderToken, amount) {
export function sendReceipt(guestFirstName: string, guestLastName: string, guestEmail: string, confirmation: string, orderId: string, orderToken: string, amount: number): void {
mailgun.messages().send({
from: 'Mustache Bash Tickets <contact@mustachebash.com>',
to: guestFirstName + ' ' + guestLastName + ' <' + guestEmail + '> ',
Expand Down Expand Up @@ -360,7 +367,7 @@ table[class=body] .article {
.catch(err => log.error({err, customerEmail, confirmation}, 'Receipt email failed to send'));
}

export function sendTransfereeConfirmation(transfereeFirstName, transfereeLastName, transfereeEmail, parentOrderId, orderToken) {
export function sendTransfereeConfirmation(transfereeFirstName: string, transfereeLastName: string, transfereeEmail: string, parentOrderId: string, orderToken: string): void {
mailgun.messages().send({
from: 'Mustache Bash Tickets <contact@mustachebash.com>',
to: transfereeFirstName + ' ' + transfereeLastName + ' <' + transfereeEmail + '> ',
Expand Down
Loading
Loading