2020-02-06 16:57:33 +01:00
'use strict' ;
exports = module . exports = {
2021-03-15 12:47:57 -07:00
add ,
get ,
update ,
del ,
2021-06-04 09:28:40 -07:00
delByAccessToken ,
delExpired ,
2021-09-17 14:32:13 -07:00
delByUserIdAndType ,
2021-06-04 09:28:40 -07:00
listByUserId ,
2021-03-15 12:47:57 -07:00
getByAccessToken ,
2020-02-06 16:57:33 +01:00
2021-03-15 12:47:57 -07:00
validateTokenType ,
2020-02-07 23:03:30 +01:00
2022-09-23 12:57:13 +02:00
hasScope ,
2020-02-06 16:57:33 +01:00
// token client ids. we categorize them so we can have different restrictions based on the client
2023-08-07 16:53:00 +02:00
ID _WEBADMIN : 'cid-webadmin' , // dashboard
ID _DEVELOPMENT : 'cid-development' , // dashboard development
ID _CLI : 'cid-cli' , // cloudron cli
ID _SDK : 'cid-sdk' , // created by user via dashboard
2022-09-22 21:58:56 +02:00
2022-09-23 12:57:13 +02:00
SCOPES : [ '*' ] //, 'apps', 'domains'],
2020-02-06 16:57:33 +01:00
} ;
2022-09-22 21:58:56 +02:00
const TOKENS _FIELDS = [ 'id' , 'accessToken' , 'identifier' , 'clientId' , 'scopeJson' , 'expires' , 'name' , 'lastUsedTime' ] . join ( ',' ) ;
2021-06-04 09:28:40 -07:00
const assert = require ( 'assert' ) ,
2020-02-06 16:57:33 +01:00
BoxError = require ( './boxerror.js' ) ,
2021-06-04 09:28:40 -07:00
database = require ( './database.js' ) ,
2020-02-06 16:57:33 +01:00
hat = require ( './hat.js' ) ,
2022-09-22 21:58:56 +02:00
safe = require ( 'safetydance' ) ,
2021-06-04 09:28:40 -07:00
uuid = require ( 'uuid' ) ;
2020-02-06 16:57:33 +01:00
2022-09-22 21:58:56 +02:00
function postProcess ( result ) {
assert . strictEqual ( typeof result , 'object' ) ;
result . scope = safe . JSON . parse ( result . scopeJson ) || { } ;
2022-09-24 17:29:42 +02:00
delete result . scopeJson ;
2022-09-22 21:58:56 +02:00
return result ;
}
2020-02-06 16:57:33 +01:00
function validateTokenName ( name ) {
assert . strictEqual ( typeof name , 'string' ) ;
2022-02-07 13:19:59 -08:00
if ( name . length > 64 ) return new BoxError ( BoxError . BAD _FIELD , 'name too long' ) ;
2020-02-06 16:57:33 +01:00
return null ;
}
2020-02-07 23:03:30 +01:00
function validateTokenType ( type ) {
2020-02-07 23:12:53 +01:00
assert . strictEqual ( typeof type , 'string' ) ;
2020-02-07 23:03:30 +01:00
2023-08-07 16:53:00 +02:00
const types = [ exports . ID _WEBADMIN , exports . ID _SDK , exports . ID _DEVELOPMENT , exports . ID _CLI ] ;
2022-09-24 17:29:42 +02:00
if ( types . indexOf ( type ) === - 1 ) return new BoxError ( BoxError . BAD _FIELD , ` type must be one of ${ types . join ( ',' ) } ` ) ;
2020-02-07 23:03:30 +01:00
return null ;
}
2022-09-22 21:58:56 +02:00
function validateScope ( scope ) {
assert . strictEqual ( typeof scope , 'object' ) ;
for ( const key in scope ) {
2022-09-24 17:29:42 +02:00
if ( exports . SCOPES . indexOf ( key ) === - 1 ) return new BoxError ( BoxError . BAD _FIELD , ` Unkown token scope ${ key } . Valid scopes are ${ exports . SCOPES . join ( ',' ) } ` ) ;
if ( scope [ key ] !== 'rw' && scope [ key ] !== 'r' ) return new BoxError ( BoxError . BAD _FIELD , ` Unkown token scope value ${ scope [ key ] } for ${ key } . Valid values are 'rw' or 'r' ` ) ;
2022-09-22 21:58:56 +02:00
}
return null ;
}
2022-09-23 12:57:13 +02:00
function hasScope ( token , method , path ) {
assert . strictEqual ( typeof token , 'object' ) ;
assert . strictEqual ( typeof method , 'string' ) ;
assert . strictEqual ( typeof path , 'string' ) ;
// TODO path checking in the future
const matchAll = token . scope [ '*' ] ;
if ( matchAll === 'rw' ) {
return true ;
} else if ( matchAll === 'r' && ( method === 'GET' || method === 'HEAD' || method === 'OPTIONS' ) ) {
return true ;
} else {
return false ;
}
}
2021-06-04 09:28:40 -07:00
async function add ( token ) {
assert . strictEqual ( typeof token , 'object' ) ;
const { clientId , identifier , expires } = token ;
const name = token . name || '' ;
2022-09-22 21:58:56 +02:00
const scope = token . scope || { '*' : 'rw' } ;
let error = validateTokenName ( name ) ;
if ( error ) throw error ;
error = validateScope ( scope ) ;
2021-06-04 09:28:40 -07:00
if ( error ) throw error ;
const id = 'tid-' + uuid . v4 ( ) ;
2023-06-02 20:47:36 +02:00
const accessToken = token . accessToken || hat ( 8 * 32 ) ;
2021-06-04 09:28:40 -07:00
2022-09-22 21:58:56 +02:00
await database . query ( 'INSERT INTO tokens (id, accessToken, identifier, clientId, expires, scopeJson, name) VALUES (?, ?, ?, ?, ?, ?, ?)' , [ id , accessToken , identifier , clientId , expires , JSON . stringify ( scope ) , name ] ) ;
2021-06-04 09:28:40 -07:00
return { id , accessToken , scope , clientId , identifier , expires , name } ;
2020-02-06 16:57:33 +01:00
}
2020-02-07 16:20:05 +01:00
2021-06-04 09:28:40 -07:00
async function get ( id ) {
2020-02-07 16:20:05 +01:00
assert . strictEqual ( typeof id , 'string' ) ;
2021-06-04 09:28:40 -07:00
const result = await database . query ( ` SELECT ${ TOKENS _FIELDS } FROM tokens WHERE id = ? ` , [ id ] ) ;
if ( result . length === 0 ) return null ;
2020-02-07 16:20:05 +01:00
2022-09-22 21:58:56 +02:00
return postProcess ( result [ 0 ] ) ;
2020-02-07 16:20:05 +01:00
}
2021-06-04 09:28:40 -07:00
async function del ( id ) {
2020-02-07 16:20:05 +01:00
assert . strictEqual ( typeof id , 'string' ) ;
2021-06-04 09:28:40 -07:00
const result = await database . query ( 'DELETE FROM tokens WHERE id = ?' , [ id ] ) ;
if ( result . affectedRows !== 1 ) throw new BoxError ( BoxError . NOT _FOUND , 'Token not found' ) ;
2020-02-07 16:20:05 +01:00
}
2021-06-04 09:28:40 -07:00
async function listByUserId ( userId ) {
2020-02-07 16:20:05 +01:00
assert . strictEqual ( typeof userId , 'string' ) ;
2022-09-22 21:58:56 +02:00
const results = await database . query ( ` SELECT ${ TOKENS _FIELDS } FROM tokens WHERE identifier = ? ` , [ userId ] ) ;
results . forEach ( postProcess ) ;
return results ;
2021-06-04 09:28:40 -07:00
}
async function getByAccessToken ( accessToken ) {
assert . strictEqual ( typeof accessToken , 'string' ) ;
2020-02-07 16:20:05 +01:00
2021-06-04 09:28:40 -07:00
const result = await database . query ( 'SELECT ' + TOKENS _FIELDS + ' FROM tokens WHERE accessToken = ? AND expires > ?' , [ accessToken , Date . now ( ) ] ) ;
if ( result . length === 0 ) return null ;
2022-09-22 21:58:56 +02:00
return postProcess ( result [ 0 ] ) ;
2020-02-07 16:20:05 +01:00
}
2021-03-15 12:47:57 -07:00
2021-06-04 09:28:40 -07:00
async function delByAccessToken ( accessToken ) {
assert . strictEqual ( typeof accessToken , 'string' ) ;
2021-03-15 12:47:57 -07:00
2021-06-04 09:28:40 -07:00
const result = await database . query ( 'DELETE FROM tokens WHERE accessToken = ?' , [ accessToken ] ) ;
if ( result . affectedRows !== 1 ) throw new BoxError ( BoxError . NOT _FOUND , 'Token not found' ) ;
}
2021-03-15 12:47:57 -07:00
2021-06-04 09:28:40 -07:00
async function delExpired ( ) {
const result = await database . query ( 'DELETE FROM tokens WHERE expires <= ?' , [ Date . now ( ) ] ) ;
return result . affectedRows ;
2021-03-15 12:47:57 -07:00
}
2021-09-17 14:32:13 -07:00
async function delByUserIdAndType ( userId , type ) {
assert . strictEqual ( typeof userId , 'string' ) ;
assert . strictEqual ( typeof type , 'string' ) ;
const result = await database . query ( 'DELETE FROM tokens WHERE identifier=? AND clientId=?' , [ userId , type ] ) ;
return result . affectedRows ;
}
2021-06-04 09:28:40 -07:00
async function update ( id , values ) {
2021-03-15 12:47:57 -07:00
assert . strictEqual ( typeof id , 'string' ) ;
assert . strictEqual ( typeof values , 'object' ) ;
2021-06-04 09:28:40 -07:00
let args = [ ] ;
let fields = [ ] ;
for ( let k in values ) {
fields . push ( k + ' = ?' ) ;
args . push ( values [ k ] ) ;
}
args . push ( id ) ;
const result = await database . query ( 'UPDATE tokens SET ' + fields . join ( ', ' ) + ' WHERE id = ?' , args ) ;
if ( result . affectedRows !== 1 ) throw new BoxError ( BoxError . NOT _FOUND , 'Token not found' ) ;
}