-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
HPC-7904: 🛂 Setup authentication for resolvers
- Loading branch information
Showing
32 changed files
with
1,743 additions
and
1,568 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
#!/bin/sh | ||
|
||
set -e | ||
|
||
USAGE="$(basename "$0") prod|ci|dev [-h] | ||
Install the npm dependencies in this repository and perform any | ||
neccesary symlink operations to use packages from these repos. | ||
where: | ||
-h, --help show this help text" | ||
|
||
MODE="" | ||
|
||
while [ "$1" != "" ]; do | ||
case $1 in | ||
prod ) shift | ||
MODE='prod' | ||
;; | ||
ci ) shift | ||
MODE='ci' | ||
;; | ||
dev ) shift | ||
MODE='dev' | ||
;; | ||
-h | --help ) echo "$USAGE" | ||
exit | ||
;; | ||
esac | ||
done | ||
|
||
if [ "$MODE" = "" ]; then | ||
echo "An install mode must be specified, e.g: npm run install-and-link dev" | ||
exit 1 | ||
fi | ||
|
||
DIR_TO_GO_BACK=$(pwd -P) | ||
|
||
echo "Installing dependencies for hpc-api" | ||
yarn install | ||
|
||
if [ "$MODE" = "dev" ]; then | ||
echo "Linking @unocha/hpc-api-core to ../hpc-api-core" | ||
rm -rf "node_modules/@unocha/hpc-api-core" | ||
ln -s "../../../hpc-api-core" "node_modules/@unocha/hpc-api-core" | ||
fi | ||
|
||
cd $DIR_TO_GO_BACK |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
#! /usr/bin/env node | ||
|
||
/* | ||
* A script that forwards port 9393 to 9339 to allow for attaching a debugger | ||
* from outside of the docker image. | ||
*/ | ||
|
||
const net = require('net'); | ||
|
||
net | ||
.createServer((from) => { | ||
try { | ||
const to = net.createConnection({ | ||
host: 'localhost', | ||
port: 9339, | ||
}); | ||
const close = () => { | ||
to.destroy(); | ||
from.destroy(); | ||
}; | ||
from.pipe(to); | ||
to.pipe(from); | ||
to.on('close', close); | ||
to.on('error', close); | ||
to.on('end', close); | ||
from.on('close', close); | ||
from.on('error', close); | ||
from.on('end', close); | ||
} catch (e) { | ||
console.log('Unable to connect'); | ||
from.destroy(); | ||
} | ||
}) | ||
.listen(9393); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# Authentication | ||
|
||
Authentication can be used as a guard on a field, query or mutation, restricting data access or actions for a specific group of users. | ||
|
||
Since the codebase uses TypeGraphQL, which relies heavily on decorators, authentication is also done using decorators. | ||
|
||
Authentication is done with use of `@Permission` decorator. This decorator takes function as an argument with permission object as a return value. | ||
|
||
For example: | ||
|
||
```lang=js | ||
@Permission(({ args }) => | ||
Promise.resolve({ | ||
or: [ | ||
{ type: 'global', permission: P.global.VIEW_ANY_PLAN_DATA }, | ||
{ type: 'plan', permission: P.plan.VIEW_DATA, id: args.id }, | ||
], | ||
}) | ||
) | ||
``` | ||
|
||
If only global permission check is needed, it can be used directly: | ||
|
||
```lang=js | ||
@Permission({ | ||
type: 'global', | ||
permission: P.global.VIEW_ALL_JOBS, | ||
}) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,75 @@ | ||
const AuthLib = { | ||
async getRolesForAParticipant(participantId: number) { | ||
return participantId; //TODO: add functionality | ||
}, | ||
|
||
async getParticipantsForATarget(target: { | ||
targetId: number; | ||
target: string; | ||
}) { | ||
return target; //TODO: add functionality | ||
}, | ||
import { Request } from '@hapi/hapi'; | ||
import { BASIC_AUTH_USER } from '@unocha/hpc-api-core/src/auth'; | ||
import { BadRequestError } from '@unocha/hpc-api-core/src/util/error'; | ||
|
||
interface BasicAuth { | ||
username: string | null; | ||
password: string | null; | ||
} | ||
|
||
const parseBasic = (credentialsPart: string): BasicAuth => { | ||
let pieces: (string | null)[]; | ||
|
||
const decoded = Buffer.from(credentialsPart, 'base64').toString('utf8'); | ||
|
||
if (!decoded) { | ||
throw new Error('Invalid authorization header'); | ||
} | ||
|
||
const index = decoded.indexOf(':'); | ||
|
||
if (index === -1) { | ||
pieces = [decoded]; | ||
} else { | ||
pieces = [decoded.slice(0, index), decoded.slice(index + 1)]; | ||
} | ||
|
||
// Allows for usernameless authentication | ||
if (!pieces[0]) { | ||
pieces[0] = null; | ||
} | ||
|
||
// Allows for passwordless authentication | ||
if (!pieces[1]) { | ||
pieces[1] = null; | ||
} | ||
|
||
return { | ||
username: pieces[0], | ||
password: pieces[1], | ||
}; | ||
}; | ||
|
||
export default AuthLib; | ||
export const getTokenFromRequest = (req: Request): string | null => { | ||
if (!req.headers?.authorization) { | ||
return null; | ||
} | ||
|
||
const pieces = req.headers.authorization.split(/\s+/); | ||
|
||
if (!pieces || pieces.length !== 2) { | ||
throw new Error('Bad HTTP authentication header format'); | ||
} | ||
|
||
const schemePart = pieces[0]; | ||
const credentialsPart = pieces[1]; | ||
|
||
switch (schemePart.toLowerCase()) { | ||
case 'basic': { | ||
const credentials = parseBasic(credentialsPart); | ||
|
||
if (credentials.username !== BASIC_AUTH_USER) { | ||
throw new BadRequestError( | ||
'Client Authentication is not supported in the v4 API' | ||
); | ||
} | ||
|
||
return credentials.password; | ||
} | ||
case 'bearer': | ||
return credentialsPart; | ||
|
||
default: | ||
throw new Error('Unsupported authorization scheme'); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { actionIsPermitted } from '@unocha/hpc-api-core/src/auth'; | ||
import { RequiredPermissionsCondition } from '@unocha/hpc-api-core/src/auth/permissions'; | ||
import { Context } from '@unocha/hpc-api-core/src/lib/context'; | ||
import { ForbiddenError } from '@unocha/hpc-api-core/src/util/error'; | ||
import { createMethodDecorator, ResolverData } from 'type-graphql'; | ||
|
||
type RequiredPermissions = ( | ||
resolverData: ResolverData<Context> | ||
) => Promise<RequiredPermissionsCondition<never>>; | ||
|
||
// eslint-disable-next-line @typescript-eslint/naming-convention | ||
export function Permission( | ||
requiredPermissions: RequiredPermissions | RequiredPermissionsCondition<never> | ||
): MethodDecorator { | ||
return createMethodDecorator( | ||
async (resolverData: ResolverData<Context>, next) => { | ||
let permissions: RequiredPermissionsCondition<never>; | ||
if (typeof requiredPermissions === 'function') { | ||
permissions = await requiredPermissions(resolverData); | ||
} else { | ||
permissions = requiredPermissions; | ||
} | ||
|
||
if (!(await actionIsPermitted(permissions, resolverData.context))) { | ||
throw new ForbiddenError('No permission to perform this action'); | ||
} | ||
|
||
return next(); | ||
} | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.