Skip to content

Commit

Permalink
feat: Adding HasDeleteRestriction user_tag (#1390)
Browse files Browse the repository at this point in the history
* Adding the type and failing HTTP DELETE operations if this tag is set.
* See nftstorage/admin.storage#66
  • Loading branch information
jsdevel authored Jun 1, 2022
1 parent b557901 commit 0c3bb58
Show file tree
Hide file tree
Showing 7 changed files with 44 additions and 7 deletions.
18 changes: 18 additions & 0 deletions packages/api/src/auth.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as JWT from './utils/jwt.js'
import {
AccountRestrictedError,
DeleteRestrictedError,
MagicTokenRequiredError,
NoTokenError,
PinningUnauthorizedError,
Expand Down Expand Up @@ -102,6 +103,23 @@ export function withAccountNotRestricted (handler) {
}
}

/**
* Middleware: verify that the authenticated request is for a user whose
* ability to delete is not restricted.
*
* @param {import('itty-router').RouteHandler} handler
* @returns {import('itty-router').RouteHandler}
*/
export function withDeleteNotRestricted (handler) {
return async (request, env, ctx) => {
const isDeleteRestricted = request.auth.userTags.find(v => (v.tag === USER_TAGS.DELETE_RESTRICTION && v.value === 'true'))
if (!isDeleteRestricted) {
return handler(request, env, ctx)
}
throw new DeleteRestrictedError()
}
}

/**
* Middleware: verify that the authenticated request is for a user who is
* authorized to pin.
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export const UPLOAD_TYPES = ['Car', 'Blob', 'Multipart', 'Upload']
export const PIN_STATUSES = ['PinQueued', 'Pinning', 'Pinned', 'PinError']
export const USER_TAGS = {
ACCOUNT_RESTRICTION: 'HasAccountRestriction',
DELETE_RESTRICTION: 'HasDeleteRestriction',
PSA_ACCESS: 'HasPsaAccess'
}
9 changes: 9 additions & 0 deletions packages/api/src/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,15 @@ export class AccountRestrictedError extends HTTPError {
}
AccountRestrictedError.CODE = 'ERROR_ACCOUNT_RESTRICTED'

export class DeleteRestrictedError extends HTTPError {
constructor (msg = 'Delete operations restricted.') {
super(msg, 403)
this.name = 'DeleteRestrictedError'
this.code = DeleteRestrictedError.CODE
}
}
DeleteRestrictedError.CODE = 'ERROR_DELETE_RESTRICTED'

export class TokenNotFoundError extends HTTPError {
constructor (msg = 'API token no longer valid') {
super(msg, 401)
Expand Down
14 changes: 10 additions & 4 deletions packages/api/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { Router } from 'itty-router'
import { errorHandler } from './error-handler.js'
import { addCorsHeaders, withCorsHeaders, corsOptions } from './cors.js'
import { withAccountNotRestricted, withApiOrMagicToken, withMagicToken, withPinningAuthorized } from './auth.js'
import { withAccountNotRestricted, withDeleteNotRestricted, withApiOrMagicToken, withMagicToken, withPinningAuthorized } from './auth.js'
import { envAll } from './env.js'
import { statusGet } from './status.js'
import { carHead, carGet, carPut, carPost } from './car.js'
Expand Down Expand Up @@ -48,9 +48,15 @@ const auth = {
// must be a logged in user
'👤': compose(withCorsHeaders, withMagicToken),

// must be a logged in user with no delete restriction
'👤🗑️': compose(withCorsHeaders, withMagicToken, withDeleteNotRestricted),

// needs PSA & restricted users allowed
'📌⚠️': compose(withCorsHeaders, withApiOrMagicToken, withPinningAuthorized),

// needs PSA & restricted users with no delete restriction allowed
'📌⚠️🗑️': compose(withCorsHeaders, withApiOrMagicToken, withDeleteNotRestricted, withPinningAuthorized),

// needs PSA
'📌': compose(withCorsHeaders, withApiOrMagicToken, withAccountNotRestricted, withPinningAuthorized) // needs PSA
}
Expand All @@ -70,17 +76,17 @@ router.post('/pins', auth['📌'](pinPost))
router.post('/pins/:requestId', auth['📌'](pinPost))
router.get('/pins/:requestId', auth['📌⚠️'](pinGet))
router.get('/pins', auth['📌⚠️'](pinsGet))
router.delete('/pins/:requestId', auth['📌⚠️'](pinDelete))
router.delete('/pins/:requestId', auth['📌⚠️🗑️'](pinDelete))

router.get('/name/:key', auth['🌍'](nameGet))
router.get('/name/:key/watch', auth['🌍'](nameWatchGet))
router.post('/name/:key', auth['🔑'](namePost))

router.delete('/user/uploads/:cid', auth['👤'](userUploadsDelete))
router.delete('/user/uploads/:cid', auth['👤🗑️'](userUploadsDelete))
router.post('/user/uploads/:cid/rename', auth['👤'](userUploadsRename))
router.get('/user/tokens', auth['👤'](userTokensGet))
router.post('/user/tokens', auth['👤'](userTokensPost))
router.delete('/user/tokens/:id', auth['👤'](userTokensDelete))
router.delete('/user/tokens/:id', auth['👤🗑️'](userTokensDelete))
router.get('/user/account', auth['👤'](userAccountGet))
router.get('/user/info', auth['👤'](userInfoGet))
/* eslint-enable no-multi-spaces */
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export async function userInfoGet (request, env) {
...user,
tags: {
HasAccountRestriction: hasTag(user, 'HasAccountRestriction', 'true'),
HasDeleteRestriction: hasTag(user, 'HasDeleteRestriction', 'true'),
HasPsaAccess: hasTag(user, 'HasPsaAccess', 'true'),
HasSuperHotAccess: hasTag(user, 'HasSuperHotAccess', 'true'),
StorageLimitBytes: getTagValue(user, 'StorageLimitBytes', '')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TYPE user_tag_type ADD VALUE 'HasDeleteRestriction';
7 changes: 4 additions & 3 deletions packages/db/postgres/tables.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
DO
DO
$$
BEGIN
-- Auth key blocked status type is the type of blocking that has occurred on the api
Expand All @@ -19,14 +19,15 @@ BEGIN
CREATE TYPE user_tag_type AS ENUM
(
'HasAccountRestriction',
'HasDeleteRestriction',
'HasPsaAccess',
'StorageLimitBytes'
);
END IF;

-- Types for notification emails
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'email_type') THEN
CREATE TYPE email_type AS ENUM
CREATE TYPE email_type AS ENUM
(
'User75PercentStorage',
'User80PercentStorage',
Expand Down Expand Up @@ -310,7 +311,7 @@ CREATE TABLE IF NOT EXISTS metric
updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('utc'::text, now()) NOT NULL
);

CREATE TABLE IF NOT EXISTS email_history
CREATE TABLE IF NOT EXISTS email_history
(
id BIGSERIAL PRIMARY KEY,
-- the id of the user being notified
Expand Down

0 comments on commit 0c3bb58

Please sign in to comment.