Skip to content

Commit

Permalink
feat: refactor pinning authorization logic to use user_tag table
Browse files Browse the repository at this point in the history
Closes #1381
  • Loading branch information
e-schneid committed Feb 16, 2022
1 parent f609c8b commit dfd253e
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 10 deletions.
3 changes: 3 additions & 0 deletions packages/api/db/reset.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ DROP TYPE IF EXISTS upload_type cascade;
DROP TYPE IF EXISTS pin_status_type cascade;
DROP TYPE IF EXISTS service_type cascade;
DROP TYPE IF EXISTS auth_key_blocked_status_type cascade;
DROP TYPE IF EXISTS user_tag_type cascade;
DROP TYPE IF EXISTS user_tag_value_type cascade;
DROP TABLE IF EXISTS upload CASCADE;
DROP TABLE IF EXISTS pin;
DROP TABLE IF EXISTS content;
DROP TABLE IF EXISTS auth_key_history;
DROP TABLE IF EXISTS auth_key;
DROP TABLE IF EXISTS public.user_tag;
DROP TABLE IF EXISTS public.user;

DROP TABLE IF EXISTS cargo.aggregate_entries;
Expand Down
7 changes: 0 additions & 7 deletions packages/api/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,6 @@ export const database = {

export const isDebug = DEBUG === 'true'

/**
* The list of user IDs that are allowed to use the Pinning Service API. By
* default ["*"] - meaning anyone can use it.
*/
export const psaAllow =
typeof PSA_ALLOW !== 'undefined' ? PSA_ALLOW.split(',') : ['*']

export const s3 = {
endpoint: typeof S3_ENDPOINT !== 'undefined' ? S3_ENDPOINT : '',
region: typeof S3_REGION !== 'undefined' ? S3_REGION : '',
Expand Down
20 changes: 18 additions & 2 deletions packages/api/src/middleware/psa.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { maybeCapture, ErrorPinningUnauthorized } from '../errors.js'
import { JSONResponse } from '../utils/json-response.js'
import { validate } from '../utils/auth.js'
import { psaAllow } from '../constants.js'

/** @typedef {import('../bindings').Handler} Handler */

Expand All @@ -28,6 +27,19 @@ export function withPsaErrorHandler(handler) {
}
}
}
/**
* Return true if a user has a tag with a given name and value.
*
* @param {import('../utils/db-client-types.js').UserOutput} user
* @param {string} tagName
* @param {string} value
* @returns {boolean}
*/
function hasTag(user, tagName, value) {
return Boolean(
user.tags?.find((tag) => tag.tag === tagName && tag.value === value)
)
}

/**
* Verify that the authenticated request is for a user who is authorized to use
Expand All @@ -40,7 +52,11 @@ export function withPinningAuthorized(handler) {
return async (event, ctx) => {
// TODO: we need withAuth middleware so we don't have to do this twice
const { user } = await validate(event, ctx)
const authorized = psaAllow.includes(String(user.id)) || psaAllow[0] === '*'

const authorized =
hasTag(user, 'PSA_ENABLED', 'true') &&
!hasTag(user, 'ACCOUNT_ENABLED', 'false')

if (!authorized) {
throw new ErrorPinningUnauthorized()
}
Expand Down
6 changes: 6 additions & 0 deletions packages/api/src/utils/db-client-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@ export type UserOutputKey = Pick<
'user_id' | 'id' | 'name' | 'secret'
>

export type UserOutputTag = Pick<
definitions['user_tag'],
'user_id' | 'id' | 'tag' | 'value'
>

export type UserOutput = definitions['user'] & {
keys: Array<UserOutputKey>
tags: Array<UserOutputTag>
}

export type UploadOutput = definitions['upload'] & {
Expand Down
31 changes: 30 additions & 1 deletion packages/api/src/utils/db-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ export class DBClient {
id,
magic_link_id,
github_id,
keys:auth_key_user_id_fkey(user_id,id,name,secret)
keys:auth_key_user_id_fkey(user_id,id,name,secret),
tags:user_tag_user_id_fkey(user_id,id,tag,value)
`
)
.or(`magic_link_id.eq.${id},github_id.eq.${id}`)
Expand Down Expand Up @@ -377,6 +378,34 @@ export class DBClient {
return data
}

/**
* Create a new user tag
*
* @param {Object} tag
* @param {number} tag.user_id
* @param {string} tag.tag
* @param {string} tag.value
* @param {string} tag.value_type
* @param {string} tag.inserted_at
* @param {string} tag.reason
*/
async createUserTag(tag) {
/** @type {PostgrestQueryBuilder<definitions['user_tag']>} */
const query = this.client.from('user_tag')

const { data, error } = await query.upsert(tag).single()

if (error) {
throw new DBError(error)
}

if (!data) {
throw new Error('User tag not created.')
}

return data
}

/**
* List auth keys
*
Expand Down
32 changes: 32 additions & 0 deletions packages/api/src/utils/db-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,38 @@ export interface definitions {
*/
updated_at: string
}
user_tag: {
/**
* Format: bigint
* @description Note:
* This is a Primary Key.<pk/>
*/
id: number
/**
* Format: bigint
* @description Note:
* This is a Foreign Key to `user.id`.<fk table='user' column='id'/>
*/
user_id: number
/** Format: text */
tag: string
/** Format: text */
value: string
/** Format: text */
user_tag_value_type: string
/** Format: text */
reason: string
/**
* Format: timestamp with time zone
* @default timezone('utc'::text, now())
*/
inserted_at: string
/**
* Format: timestamp with time zone
* @default timezone('utc'::text, now())
*/
deleted_at?: string
}
}

export interface parameters {
Expand Down
17 changes: 17 additions & 0 deletions packages/api/test/scripts/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,23 @@ export async function createTestUserWithFixedToken({
userId: user.id,
})

await client.createUserTag({
user_id: user.id,
tag: 'PSA_ENABLED',
value: 'true',
value_type: 'boolean',
reason: '',
inserted_at: '2/22/2022',
})

await client.createUserTag({
user_id: user.id,
tag: 'ACCOUNT_ENABLED',
value: 'true',
value_type: 'boolean',
reason: '',
inserted_at: '2/22/2022',
})
return { token, userId: user.id, githubId: user.github_id }
}

Expand Down

0 comments on commit dfd253e

Please sign in to comment.