11import axios from 'axios' ;
22import config from 'config' ;
33
4- import { Action } from '../utils/enums/application.ts' ;
5- import { Problem , uuidValidateV4 } from '../utils/index.ts' ;
4+ import { Action , GroupName } from '../utils/enums/application.ts' ;
5+ import { getCurrentSubject , Problem , uuidValidateV4 } from '../utils/index.ts' ;
6+ import { getSubjectGroups } from './yars.ts' ;
67
78import type { AxiosInstance , AxiosRequestConfig } from 'axios' ;
9+ import type { PrismaTransactionClient } from '../db/dataConnection.ts' ;
10+ import type { CurrentContext } from '../types/stuff' ;
11+
12+ /**
13+ * PCNS Groups to COMS Permission mappings
14+ */
15+ const COMS_PERM_MAP = new Map < GroupName , Action [ ] > ( [
16+ [ GroupName . PROPONENT , [ Action . CREATE ] ] ,
17+ [ GroupName . NAVIGATOR_READ_ONLY , [ Action . READ ] ] ,
18+ [ GroupName . NAVIGATOR , [ Action . CREATE , Action . READ , Action . UPDATE , Action . DELETE ] ] ,
19+ [ GroupName . SUPERVISOR , [ Action . CREATE , Action . READ , Action . UPDATE , Action . DELETE ] ] ,
20+ [ GroupName . ADMIN , [ Action . CREATE , Action . READ , Action . UPDATE , Action . DELETE ] ] ,
21+ [ GroupName . DEVELOPER , [ Action . CREATE , Action . READ , Action . UPDATE , Action . DELETE ] ]
22+ ] ) ;
823
924/**
1025 * Returns an Axios instance for the COMS API
26+ * Injects pseudo service account basic auth if no other auth provided
1127 * @param options Axios request config options
1228 * @returns An axios instance
1329 */
1430function comsAxios ( options : AxiosRequestConfig = { } ) : AxiosInstance {
31+ // Inject pseudo service account if no other auth provided
32+ if ( ! options . headers ?. Authorization ) {
33+ const ak = config . get < string > ( 'server.objectStorage.accessKeyId' ) ;
34+ const sak = config . get < string > ( 'server.objectStorage.secretAccessKey' ) ;
35+ const b64 = Buffer . from ( ak + ':' + sak ) . toString ( 'base64' ) ;
36+
37+ if ( ! options . headers ) options . headers = { } ;
38+ options . headers . Authorization = `Basic ${ b64 } ` ;
39+ options . headers [ 'x-amz-bucket' ] = config . get ( 'server.objectStorage.bucket' ) ;
40+ options . headers [ 'x-amz-endpoint' ] = config . get ( 'server.objectStorage.endpoint' ) ;
41+ }
42+
1543 // Create axios instance
1644 const instance = axios . create ( {
1745 baseURL : config . get ( 'frontend.coms.apiPath' ) ,
@@ -23,27 +51,20 @@ function comsAxios(options: AxiosRequestConfig = {}): AxiosInstance {
2351}
2452
2553/**
26- * Creates a bucket record. Bucket should exist in S3. If the set of bucket, endpoint and key match
27- * an existing record, the user will be added to that existing bucket with the provided permissions
28- * instead of generating a new bucket record.
29- * This endpoint can be used to grant the current user permission to upload to a new or existing bucket.
30- * @param bearerToken The bearer token of the authorized user
31- * @param permissions An array of permissions to grant the user
54+ * Obtain a bucket record. This actually utilizes the COMS create bucket endpoint as its the only way to
55+ * obtain full bucket information using their pseudo service account access.
3256 * @returns The created bucket data
3357 */
34- export const createBucket = async ( bearerToken : string , permissions : Action [ ] ) => {
35- const { data } = await comsAxios ( {
36- headers : { Authorization : `Bearer ${ bearerToken } ` }
37- } ) . put ( '/bucket' , {
58+ export const getBucket = async ( ) => {
59+ const { status, headers, data } = await comsAxios ( ) . put ( '/bucket' , {
3860 accessKeyId : config . get ( 'server.objectStorage.accessKeyId' ) ,
3961 bucket : config . get ( 'server.objectStorage.bucket' ) ,
4062 bucketName : 'PCNS' ,
4163 endpoint : config . get ( 'server.objectStorage.endpoint' ) ,
4264 secretAccessKey : config . get ( 'server.objectStorage.secretAccessKey' ) ,
43- key : config . get ( 'server.objectStorage.key' ) ,
44- permCodes : permissions
65+ key : config . get ( 'server.objectStorage.key' )
4566 } ) ;
46- return data ;
67+ return { status , headers , data } ;
4768} ;
4869
4970/**
@@ -62,3 +83,61 @@ export const getObject = async (bearerToken: string, objectId: string) => {
6283 } ) . get ( `/object/${ objectId } ` ) ;
6384 return { status, headers, data } ;
6485} ;
86+
87+ /**
88+ * Obtain the current user information in COMS
89+ * @param currentContext The current context of the Express request
90+ * @returns The COMS response
91+ */
92+ export const searchUser = async ( currentContext : CurrentContext ) => {
93+ const { status, headers, data } = await comsAxios ( {
94+ headers : { Authorization : `Bearer ${ currentContext . bearerToken } ` }
95+ // Update to use username (sub) if COMS ever opens that up
96+ } ) . get ( '/user' , { params : { identityId : getCurrentSubject ( currentContext ) . split ( '@' ) [ 0 ] . toUpperCase ( ) } } ) ;
97+ return { status, headers, data } ;
98+ } ;
99+
100+ /**
101+ * Assigns COMS permissions to the current user based on their current groups
102+ * @param tx Prisma transaction client
103+ * @param currentContext The current context of the Express request
104+ */
105+ export const assignPermissions = async ( tx : PrismaTransactionClient , currentContext : CurrentContext ) => {
106+ const sub = currentContext . tokenPayload ?. sub ;
107+
108+ if ( ! sub ) {
109+ throw new Problem ( 403 , {
110+ detail : 'Unable to obtain token sub'
111+ } ) ;
112+ }
113+
114+ const groups = await getSubjectGroups ( tx , sub ) ;
115+ const groupNames = new Set ( groups . map ( ( x ) => x . name ) ) ;
116+
117+ const actions = new Set < Action > ( [ ...groupNames ] . flatMap ( ( groupName ) => COMS_PERM_MAP . get ( groupName ) ?? [ ] ) ) ;
118+
119+ if ( actions . size > 0 && currentContext ) {
120+ try {
121+ const [ user , bucket ] = await Promise . all ( [ searchUser ( currentContext ) , getBucket ( ) ] ) ;
122+
123+ const { userId } = user . data [ 0 ] ;
124+ const { bucketId } = bucket . data ;
125+
126+ if ( ! userId || ! bucketId ) {
127+ throw new Error ( 'Unable to obtain userId or bucketId' ) ;
128+ }
129+
130+ const permissions = [ ...actions ] . flatMap ( ( action ) => ( { permCode : action , userId } ) ) ;
131+
132+ await comsAxios ( ) . put ( `/permission/bucket/${ bucketId } ` , permissions ) ;
133+ } catch ( e ) {
134+ if ( axios . isAxiosError ( e ) ) {
135+ const detail = e . response ?. data . detail ;
136+ const status = e . response ? e . response . status : 500 ;
137+ throw new Problem ( status , { detail } , { extra : { comsError : e . response ?. data } } ) ;
138+ } else {
139+ throw new Problem ( 500 , { detail : 'Server Error' } ) ;
140+ }
141+ }
142+ }
143+ } ;
0 commit comments