From 6fcd38923506965012505473b57afde2ce22973e Mon Sep 17 00:00:00 2001 From: e-schneid <99349687+e-schneid@users.noreply.github.com> Date: Tue, 17 May 2022 15:26:31 -0700 Subject: [PATCH] feat: return unique error message when using a blocked key (#1898) --- packages/api/src/errors.js | 10 ++++++++ packages/api/src/utils/auth.js | 29 ++++++++++++++++++++--- packages/api/src/utils/db-client-types.ts | 2 +- packages/api/src/utils/db-client.js | 23 +++++++++++++++--- packages/api/test/db-client.spec.js | 4 ++-- 5 files changed, 59 insertions(+), 9 deletions(-) diff --git a/packages/api/src/errors.js b/packages/api/src/errors.js index 1f121eeb35..f83910a33e 100644 --- a/packages/api/src/errors.js +++ b/packages/api/src/errors.js @@ -116,6 +116,16 @@ export class ErrorUserNotFound extends Error { } ErrorUserNotFound.CODE = 'ERROR_USER_NOT_FOUND' +export class ErrorTokenBlocked extends Error { + constructor(msg = 'API Key is blocked.') { + super(msg) + this.name = 'TokenBlocked' + this.status = 403 + this.code = ErrorTokenBlocked.CODE + } +} +ErrorTokenBlocked.CODE = 'ERROR_TOKEN_BLOCKED' + export class ErrorTokenNotFound extends Error { constructor(msg = 'API Key not found.') { super(msg) diff --git a/packages/api/src/utils/auth.js b/packages/api/src/utils/auth.js index 71c6dbf1dd..947aa64bc2 100644 --- a/packages/api/src/utils/auth.js +++ b/packages/api/src/utils/auth.js @@ -5,11 +5,24 @@ import { ErrorUserNotFound, ErrorTokenNotFound, ErrorUnauthenticated, + ErrorTokenBlocked, } from '../errors.js' import { parseJWT, verifyJWT } from './jwt.js' export const magic = new Magic(secrets.magic) import * as Ucan from 'ucan-storage/ucan-storage' +/** + * + * @param {import('./db-client-types.js').UserOutput} user + * @returns + */ +function filterDeletedKeys(user) { + return { + ...user, + keys: user.keys.filter((k) => k.deleted_at === null), + } +} + /** * Validate auth * @@ -27,7 +40,7 @@ export async function validate(event, { log, db, ucanService }, options) { const user = await db.getUser(root.audience()) if (user) { return { - user: user, + user: filterDeletedKeys(user), db, ucan: { token, root: root._decoded.payload, cap }, type: 'ucan', @@ -45,11 +58,21 @@ export async function validate(event, { log, db, ucanService }, options) { if (user) { const key = user.keys.find((k) => k?.secret === token) if (key) { + if (key.deleted_at) { + const isBlocked = await db.checkIfTokenBlocked(key) + + if (isBlocked) { + throw new ErrorTokenBlocked() + } else { + throw new ErrorUserNotFound() + } + } + log.setUser({ id: user.id, }) return { - user: user, + user: filterDeletedKeys(user), key, db, type: 'key', @@ -72,7 +95,7 @@ export async function validate(event, { log, db, ucanService }, options) { }) return { - user, + user: filterDeletedKeys(user), db, type: 'session', } diff --git a/packages/api/src/utils/db-client-types.ts b/packages/api/src/utils/db-client-types.ts index e8543cee4b..f6157d31bf 100644 --- a/packages/api/src/utils/db-client-types.ts +++ b/packages/api/src/utils/db-client-types.ts @@ -15,7 +15,7 @@ export type UpsertUserInput = Pick< export type UserOutputKey = Pick< definitions['auth_key'], - 'user_id' | 'id' | 'name' | 'secret' + 'user_id' | 'id' | 'name' | 'secret' | 'deleted_at' > export type UserOutputTag = Pick< diff --git a/packages/api/src/utils/db-client.js b/packages/api/src/utils/db-client.js index 975c0b85cb..2c1e29970e 100644 --- a/packages/api/src/utils/db-client.js +++ b/packages/api/src/utils/db-client.js @@ -91,14 +91,12 @@ export class DBClient { magic_link_id, github_id, did, - keys:auth_key_user_id_fkey(user_id,id,name,secret), + keys:auth_key_user_id_fkey(user_id,id,name,secret,deleted_at), tags:user_tag_user_id_fkey(user_id,id,tag,value) ` ) .or(`magic_link_id.eq.${id},github_id.eq.${id},did.eq.${id}`) // @ts-ignore - .filter('keys.deleted_at', 'is', null) - // @ts-ignore .filter('tags.deleted_at', 'is', null) const { data, error, status } = await select.single() @@ -113,6 +111,25 @@ export class DBClient { return data } + /** + * + * @param {import('./db-client-types').UserOutputKey} key + */ + async checkIfTokenBlocked(key) { + const { data, error } = await this.client + .from('auth_key_history') + .select('status') + .eq('auth_key_id', key.id) + .filter('deleted_at', 'is', null) + .single() + + if (error) { + throw new DBError(error) + } + + return data?.status === 'Blocked' + } + /** * Create upload with content and pins * diff --git a/packages/api/test/db-client.spec.js b/packages/api/test/db-client.spec.js index 83a0f84c65..ee42db2603 100644 --- a/packages/api/test/db-client.spec.js +++ b/packages/api/test/db-client.spec.js @@ -11,7 +11,7 @@ describe('DB Client', () => { client = await createClientWithUser() }) - it('getUser should list only active keys', async () => { + it('getUser should list all keys', async () => { const issuer1 = `did:eth:0x73573${Date.now()}` const token1 = await signJWT( { @@ -51,7 +51,7 @@ describe('DB Client', () => { throw new Error('no user data') } const keys = user.keys - assert.equal(keys.length, 2) + assert.equal(keys.length, 3) assert.equal(keys[1].name, 'key1') }) })