-
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.
feat: use mongo in lieu of couch to power data. Add auth middleware. …
- Loading branch information
1 parent
8c1f768
commit 77684c0
Showing
10 changed files
with
1,978 additions
and
181 deletions.
There are no files selected for viewing
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 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,17 @@ | ||
export { z } from 'https://deno.land/x/[email protected]/mod.ts'; | ||
export type { | ||
NextFunction, | ||
Opine, | ||
OpineRequest, | ||
OpineResponse, | ||
} from 'https://deno.land/x/[email protected]/mod.ts'; | ||
// See https://deno.land/manual@v1.31.1/advanced/typescript/types#providing-types-when-importing | ||
// @deno-types="npm:@types/express@^4.17" | ||
export { default as express } from 'npm:[email protected]'; | ||
|
||
export { default as hyper } from 'https://x.nest.land/[email protected]/mod.js'; | ||
export { default as app } from 'https://x.nest.land/[email protected]/mod.js'; | ||
export * as jwt from 'https://deno.land/x/[email protected]/mod.ts'; | ||
|
||
export { default as couchdb } from 'https://x.nest.land/[email protected]/mod.js'; | ||
export { default as redis } from 'https://x.nest.land/[email protected]/mod.js'; | ||
export { default as elasticsearch } from 'https://x.nest.land/[email protected]/mod.js'; | ||
// hyper core | ||
export { default as hyper } from 'https://raw.githubusercontent.com/hyper63/hyper/hyper%40v4.2.0/packages/core/mod.ts'; | ||
// hyper driving adapter | ||
export { default as app } from 'https://raw.githubusercontent.com/hyper63/hyper/hyper-app-express%40v1.2.0/packages/app-express/mod.ts'; | ||
|
||
// hyper driven adapters | ||
export { default as mongodb } from 'https://raw.githubusercontent.com/hyper63/hyper-adapter-mongodb/v3.1.3/mod.ts'; | ||
export { default as redis } from 'https://raw.githubusercontent.com/hyper63/hyper-adapter-redis/v3.0.0/mod.js'; | ||
export { default as hooks } from 'https://raw.githubusercontent.com/hyper63/hyper-adapter-hooks/v1.0.6/mod.js'; | ||
export { default as elasticsearch } from 'https://raw.githubusercontent.com/hyper63/hyper-adapter-elasticsearch/v2.0.2/mod.js'; |
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,34 +1,59 @@ | ||
import { authMiddleware } from './auth.ts'; | ||
import { app, couchdb, elasticsearch, hyper, redis, z } from './deps.ts'; | ||
import { app, elasticsearch, type express, hyper, mongodb, redis } from './deps.ts'; | ||
import { env, verifyAuthorizationHeader } from './utils.ts'; | ||
|
||
function env(key: string): string { | ||
const res = z.string().min(1).safeParse(Deno.env.get(key)); | ||
if (!res.success) { | ||
console.error(`Error with ENV VAR: ${key}`); | ||
throw res.error; | ||
} | ||
return res.data; | ||
} | ||
/** | ||
* Given a sub and secret, return a hyper middleware that will | ||
* check that all incoming requests have a properly signed jwt token | ||
* in the authorization header | ||
*/ | ||
const authMiddleware = | ||
({ sub, secret }: { sub: string; secret: string }) => (app: express.Express) => { | ||
const verify = verifyAuthorizationHeader({ sub, secret }); | ||
app.use(async (req, _res, next) => { | ||
await verify(req.get('authorization') || 'Bearer notoken') | ||
.then(() => next()) | ||
// pass error to next, triggering the next error middleware to take over | ||
.catch(next); | ||
}); | ||
|
||
const COUCH = `http://${env('COUCHDB_USER')}:${env('COUCHDB_PASSWORD')}@${ | ||
env( | ||
'COUCHDB_HOST', | ||
) | ||
}:5984`; | ||
app.use( | ||
( | ||
// deno-lint-ignore no-explicit-any | ||
err: any, | ||
_req: express.Request, | ||
res: express.Response, | ||
next: express.NextFunction, | ||
): unknown => { | ||
if (err && err.name === 'UnauthorizedError') { | ||
return res.status(401).send({ ok: false, msg: 'not authorized' }); | ||
} | ||
// Trigger the next error handler | ||
next(err); | ||
}, | ||
); | ||
|
||
const REDIS = { | ||
hostname: env('REDIS_HOST'), | ||
port: env('REDIS_PORT'), | ||
}; | ||
|
||
const ELASTICSEARCH = `http://${env('ELASTICSEARCH_HOST')}`; | ||
return app; | ||
}; | ||
|
||
export default hyper({ | ||
app, | ||
adapters: [ | ||
{ port: 'data', plugins: [couchdb({ url: COUCH })] }, | ||
{ port: 'cache', plugins: [redis(REDIS)] }, | ||
{ port: 'search', plugins: [elasticsearch({ url: ELASTICSEARCH })] }, | ||
{ | ||
port: 'data', | ||
plugins: [ | ||
mongodb({ | ||
url: `mongodb://${env('MONGO_USERNAME')}:${env('MONGO_PASSWORD')}@${env('MONGO_HOST')}`, | ||
}), | ||
], | ||
}, | ||
{ | ||
port: 'cache', | ||
plugins: [ | ||
// @ts-ignore incorrect types in the adapter, so safe to ignore for now | ||
redis({ hostname: env('REDIS_HOST'), port: env('REDIS_PORT') }), | ||
], | ||
}, | ||
{ port: 'search', plugins: [elasticsearch({ url: `http://${env('ELASTICSEARCH_HOST')}` })] }, | ||
], | ||
middleware: [authMiddleware(env('SECRET'))], | ||
middleware: [authMiddleware({ sub: env('SUB'), secret: env('SECRET') })], | ||
}); |
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,75 @@ | ||
import { assert, assertEquals, assertThrows } from 'https://deno.land/[email protected]/assert/mod.ts'; | ||
import * as jwt from 'https://deno.land/x/[email protected]/mod.ts'; | ||
|
||
import { env, verifyAuthorizationHeader } from './utils.ts'; | ||
|
||
Deno.test('utils', async (t) => { | ||
await t.step('env', async (t) => { | ||
await t.step('it should return the environment variable value', () => { | ||
Deno.env.set('TEST', 'foobar'); | ||
assertEquals(env('TEST'), 'foobar'); | ||
}); | ||
|
||
await t.step( | ||
'it should throw if the environment variable is not defined', | ||
() => { | ||
Deno.env.delete('TEST'); | ||
assertThrows(() => env('TEST')); | ||
}, | ||
); | ||
}); | ||
|
||
await t.step('verifyAuthorizationHeader', async (t) => { | ||
const verify = verifyAuthorizationHeader({ sub: 'foo', secret: 'bar' }); | ||
|
||
// deno-lint-ignore no-explicit-any | ||
const createToken = ({ sub, secret }: any, headers: any) => | ||
jwt.create(headers, { sub }, secret); | ||
|
||
await t.step('it should verify the header value', async () => { | ||
const token = await createToken( | ||
{ sub: 'foo', secret: 'bar' }, | ||
{ alg: 'HS256', type: 'JWT' }, | ||
); | ||
await verify(`Bearer ${token}`) | ||
.then(() => assert(true)) | ||
.catch((err) => assert(false, err)); | ||
}); | ||
|
||
await t.step( | ||
'it should throw an UnauthorizedError if token signing verification fails', | ||
async () => { | ||
const token = await createToken( | ||
{ sub: 'foo', secret: 'NOT_RIGHT' }, | ||
{ | ||
alg: 'HS256', | ||
type: 'JWT', | ||
}, | ||
); | ||
await verify(`Bearer ${token}`) | ||
.then(() => assert(false, 'should have thrown')) | ||
.catch((err) => { | ||
assertEquals(err.name, 'UnauthorizedError'); | ||
}); | ||
}, | ||
); | ||
|
||
await t.step( | ||
'it should throw an UnauthorizedError if the sub in the payload is incorrect', | ||
async () => { | ||
const token = await createToken( | ||
{ sub: 'NOT_RIGHT', secret: 'bar' }, | ||
{ | ||
alg: 'HS256', | ||
type: 'JWT', | ||
}, | ||
); | ||
await verify(`Bearer ${token}`) | ||
.then(() => assert(false, 'should have thrown')) | ||
.catch((err) => { | ||
assertEquals(err.name, 'UnauthorizedError'); | ||
}); | ||
}, | ||
); | ||
}); | ||
}); |
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,23 @@ | ||
import { jwt, z } from './deps.ts'; | ||
|
||
export function env(key: string): string { | ||
const res = z.string().min(1).safeParse(Deno.env.get(key)); | ||
if (!res.success) { | ||
console.error(`Error with ENV VAR: ${key}`); | ||
throw res.error; | ||
} | ||
return res.data; | ||
} | ||
|
||
export const verifyAuthorizationHeader = | ||
({ sub, secret }: { sub: string; secret: string }) => async (header: string) => { | ||
const payload = await jwt | ||
.verify(header.split(' ').pop() as string, secret, 'HS256') | ||
.catch(() => { | ||
throw { name: 'UnauthorizedError' }; | ||
}); | ||
/** | ||
* Confirm sub matches | ||
*/ | ||
if (payload.sub !== sub) throw { name: 'UnauthorizedError' }; | ||
}; |
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,6 +1 @@ | ||
# The official CouchDB Docker image | ||
FROM couchdb:3 | ||
|
||
# Setup CouchDB to run as a single node | ||
RUN echo '[couchdb]' > /opt/couchdb/etc/local.d/10-single-node.ini | ||
RUN echo 'single_node=true' >> /opt/couchdb/etc/local.d/10-single-node.ini | ||
FROM mongo:7.0.0 |
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
Oops, something went wrong.