18 Commits
1.0.8 ... main

Author SHA1 Message Date
850860a335 Merge branch 'develop' 2023-06-01 00:11:35 -04:00
ecb9a95397 Working!
All checks were successful
continuous-integration/drone/push Build is passing
2023-06-01 00:10:54 -04:00
447e5768a4 Move shit out to the common package to reduce duplicate bullshit 2023-05-30 20:23:57 -04:00
117c78e7a9 Merge pull request 'develop' (#2) from develop into main
Reviewed-on: #2
2023-05-26 16:49:33 +00:00
95ed9bf158 Hehehehe
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-05-26 12:19:35 -04:00
9dc3520989 Finishing touches and version bump... let the development continue!
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-26 12:10:06 -04:00
62c50964fa Yay!
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-26 12:05:46 -04:00
392414354b staging
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-26 11:26:49 -04:00
224eeae092 trying again
Some checks failed
continuous-integration/drone/push Build is failing
2023-05-26 11:23:56 -04:00
64c4345c28 Hmm...
Some checks failed
continuous-integration/drone/push Build is failing
2023-05-26 11:22:24 -04:00
6921bea5b5 Lets try this... kickstart for the db...
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-26 11:06:37 -04:00
ebe086e70f oops
All checks were successful
continuous-integration/drone/push Build is passing
2023-05-26 10:24:49 -04:00
af943c1e84 done test update 2023-05-26 10:22:07 -04:00
50af0b3065 Merge branch 'main' of ssh://git.mifi.dev:12022/mifi/auth-service
# Conflicts:
#	Dockerfile
2023-05-26 10:21:43 -04:00
50e691cf05 Why do I suck at Dockerfiles? 2023-05-26 10:17:36 -04:00
e706ef1924 Why do I suck at Dockerfiles? 2023-05-26 09:35:56 -04:00
0ad99d3f0a Maybe with ENV vars? 2023-05-26 09:13:02 -04:00
bb7b15351d More attempts at publishing... 2023-05-26 09:08:33 -04:00
33 changed files with 1296 additions and 1640 deletions

View File

@@ -91,7 +91,7 @@ steps:
commands: commands:
- yarn set version stable - yarn set version stable
- yarn install - yarn install
- yarn build:production - yarn build:package
volumes: volumes:
- name: yarnrc - name: yarnrc
path: /drone/auth/.yarnrc.yml path: /drone/auth/.yarnrc.yml
@@ -126,9 +126,9 @@ steps:
repo: git.mifi.dev/mifi/auth-service repo: git.mifi.dev/mifi/auth-service
registry: git.mifi.dev registry: git.mifi.dev
build_args: build_args:
MONGO_ENTRY_FILE: latest - MONGO_ENTRY_FILE=latest
MONGO_VERSION: latest - MONGO_VERSION=latest
NPM_TOKEN: - NPM_TOKEN:
from_secret: reg_token from_secret: reg_token
ssh-agent-key: ssh-agent-key:
from_secret: reg_token from_secret: reg_token
@@ -209,7 +209,7 @@ steps:
template: | template: |
{ {
"icon_url":"https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/apple/198/freezing-face_1f976.png", "icon_url":"https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/apple/198/freezing-face_1f976.png",
"text": "[{{ repo.name }} - Build # {{ build.number }}] Deploy {{ build.status }} {{#success build.status}}:tada:{{else}}:poop:{{/success}}", "text": "[{{ repo.name }} - Build # {{ build.number }}] Staging Deploy {{ build.status }} {{#success build.status}}:tada:{{else}}:poop:{{/success}}",
"username":"DroneBot" "username":"DroneBot"
} }
when: when:
@@ -250,25 +250,9 @@ clone:
steps: steps:
- name: Deploy Container - name: Deploy Container
image: docker image: plugins/webhook
privileged: true settings:
environment: urls: https://portainer.mifi.dev/api/stacks/webhooks/968d2244-2548-4f0b-8c18-bbc9bc35305d
CONTAINER_PREFIX: staging
HOST: area51.mifi.dev
ROUTE_PREFIX: /auth
PORT: 9001
commands:
- docker compose -f docker-compose.production-build.yml pull
- docker compose -f docker-compose.production-build.yml build --no-cache
- docker compose -f docker-compose.production-build.yml rm --stop
- docker compose -f docker-compose.production-build.yml up --wait
volumes:
- name: env-secrets
path: /drone/auth/production.env
- name: dockersock
path: /var/run/docker.sock
- name: dockerconfig
path: /drone/auth/.docker/config.json
- name: Send Status Notifications - name: Send Status Notifications
image: plugins/webhook image: plugins/webhook
privileged: true privileged: true
@@ -278,7 +262,7 @@ steps:
template: | template: |
{ {
"icon_url":"https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/apple/198/freezing-face_1f976.png", "icon_url":"https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/apple/198/freezing-face_1f976.png",
"text": "[{{ repo.name }} - Build # {{ build.number }}] Deploy {{ build.status }} {{#success build.status}}:tada:{{else}}:poop:{{/success}}", "text": "[{{ repo.name }} - Build # {{ build.number }}] Production Deploy {{ build.status }} {{#success build.status}}:tada:{{else}}:poop:{{/success}}",
"username":"DroneBot" "username":"DroneBot"
} }
when: when:
@@ -286,19 +270,8 @@ steps:
- success - success
- failure - failure
volumes:
- name: dockerconfig
host:
path: /volume1/docker/dockerconfig.json
- name: dockersock
host:
path: /var/run/docker.sock
- name: env-secrets
host:
path: /volume1/docker/beethoven/labs-auth/staging.env
depends_on: depends_on:
- Test Pipeline - Publish Pipeline
trigger: trigger:
event: event:

View File

@@ -7,6 +7,5 @@
.yarnrc.yml .yarnrc.yml
babel.config.* babel.config.*
jest.config.* jest.config.*
src
tsconfig*.json tsconfig*.json
tslint.json tslint.json

View File

@@ -1,20 +1,20 @@
ARG ENV=production ARG ENV=production
ARG PORT=9001 ARG MONGO_ENTRY_FILE
ARG MONGO_VERSION=latest
ARG NPM_TOKEN ARG NPM_TOKEN
ARG PORT=9001
ARG YARN_VERSION=3.5.0 ARG YARN_VERSION=3.5.0
ARG MONGO_ENTRY_FILE=${MONGO_VERSION:-latest}
ARG MONGO_VERSION=${MONGO_VERSION:-latest}
## mongo build stage ## mongo build stage
FROM mongo:$MONGO_VERSION AS database FROM mongo:${MONGO_VERSION} AS database
ENV NODE_ENV $ENV ARG MONGO_ENTRY_FILE=${MONGO_ENTRY_FILE:-latest}
COPY docker-entrypoint-initdb.d/mongo-init-$MONGO_ENTRY_FILE.sh ./docker-entrypoint-initdb.d/mongo-init.sh COPY docker-entrypoint-initdb.d/mongo-init-${MONGO_ENTRY_FILE}.sh ./docker-entrypoint-initdb.d/mongo-init.sh
## stage one, build the service ## stage one, build the service
FROM node:20-bullseye-slim AS build FROM node:20-bullseye-slim AS build
ENV YARN_VERSION $YARN_VERSION ENV YARN_VERSION=${YARN_VERSION}
ENV NODE_ENV development ENV NODE_ENV=development
ENV NPM_TOKEN $NPM_TOKEN ENV NPM_TOKEN=${NPM_TOKEN}
WORKDIR /home/node/app WORKDIR /home/node/app
COPY .npmrc /root COPY .npmrc /root
COPY .build.yarnrc.yml /root/.yarnrc.yml COPY .build.yarnrc.yml /root/.yarnrc.yml
@@ -24,9 +24,9 @@ RUN yarn build:production
## this is stage two , where the app actually runs ## this is stage two , where the app actually runs
FROM node:20-bullseye-slim AS containerize FROM node:20-bullseye-slim AS containerize
ENV YARN_VERSION $YARN_VERSION ENV YARN_VERSION=${YARN_VERSION}
ENV NODE_ENV $ENV ENV NODE_ENV=${ENV}
ENV NPM_TOKEN $NPM_TOKEN ENV NPM_TOKEN=${NPM_TOKEN}
WORKDIR /home/node/app WORKDIR /home/node/app
COPY .npmrc /root COPY .npmrc /root
COPY .build.yarnrc.yml /root/.yarnrc.yml COPY .build.yarnrc.yml /root/.yarnrc.yml

View File

@@ -9,7 +9,8 @@ services:
context: . context: .
target: database target: database
args: args:
MONGO_VERSION: 4.4 - MONGO_ENTRY_FILE=4.4
- MONGO_VERSION=4.4
networks: networks:
- auth-backend - auth-backend
volumes: volumes:
@@ -55,4 +56,4 @@ networks:
volumes: volumes:
auth-db: auth-db:
external: false external: true

View File

@@ -3,41 +3,57 @@ version: '3.8'
services: services:
auth-service_mongo: auth-service_mongo:
container_name: ${CONTAINER_PREFIX}-auth-service_mongo container_name: ${CONTAINER_PREFIX}-auth-service_mongo
env_file:
- staging.env
networks: networks:
- auth-backend
- docknet - docknet
volumes: volumes:
- auth-db:/data - auth-db:/data/db
- ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro - auth-db:/data/configdb
restart: unless-stopped restart: unless-stopped
image: mongo:4.4 image: mongo:${MONGO_VERSION}
auth-service: auth-service:
env_file:
- staging.env
container_name: ${CONTAINER_PREFIX}-auth-service container_name: ${CONTAINER_PREFIX}-auth-service
environment: environment:
- DB_HOST=${CONTAINER_PREFIX}-auth-service_mongo - DB_HOST=${CONTAINER_PREFIX}-auth-service_mongo
- DB_NAME=${DB_NAME}
- DB_PASSWORD=${DB_PASSWORD}
- DB_USERNAME=${DB_USERNAME}
- DEFAULT_TOKEN_DAYS=${DEFAULT_TOKEN_DAYS}
- JWT_AUDIENCE=${JWT_AUDIENCE}
- JWT_ISSUER=${JWT_ISSUER}
- JWT_SECRET=${JWT_SECRET}
- LOGIN_ROUTE=${LOGIN_ROUTE}
- LOGIN_VALID_TIME=${LOGIN_VALID_TIME}
- PORT=${PORT}
- RESET_ROUTE=${RESET_ROUTE}
- RESET_VALID_MINUTES=${RESET_VALID_MINUTES}
- ROUTE_PREFIX=${ROUTE_PREFIX}
- SESSION_KEY=${SESSION_KEY}
labels: labels:
- 'traefik.enable=true' - 'traefik.enable=true'
- 'traefik.http.routers.grow.rule=Host(`${HOST}`) && Path(`${ROUTE_PREFIX}`)' - 'traefik.docker.network=docknet'
- 'traefik.http.routers.grow.entrypoints=websecure' - 'traefik.http.routers.auth.rule=Host(`${HOST}`) && PathPrefix(`${ROUTE_PREFIX}`)'
- 'traefik.http.routers.grow.tls=true' - 'traefik.http.routers.auth.entrypoints=websecure'
- 'traefik.http.routers.grow.tls.certresolver=letsencrypt' - 'traefik.http.routers.auth.tls=true'
- 'traefik.http.routers.grow.service=grow-service' - 'traefik.http.routers.auth.tls.certresolver=letsencrypt'
- 'traefik.http.services.grow-service.loadbalancer.server.port=${PORT}' - 'traefik.http.routers.auth.service=auth-service'
- 'traefik.http.services.auth-service.loadbalancer.server.port=${PORT}'
networks: networks:
- auth-backend
- docknet - docknet
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
- auth-service_mongo - auth-service_mongo
image: git.mifi.dev/mifi/mifi/auth:latest image: git.mifi.dev/mifi/auth-service:latest
networks: networks:
auth-backend:
driver: bridge
external: false
docknet: docknet:
name: docknet name: docknet
external: true external: true
volumes: volumes:
auth-db: auth-db:
external: false external: true

View File

@@ -1,10 +1,11 @@
{ {
"name": "@mifi/auth-service", "name": "@mifi/auth-service",
"version": "1.0.8", "version": "1.0.17",
"author": "mifi (Mike Fitzpatrick)", "author": "mifi (Mike Fitzpatrick)",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"build": "tsc", "build": "tsc",
"build:package": "tsc -p ./tsconfig.package.json",
"build:production": "tsc -p ./tsconfig.production.json", "build:production": "tsc -p ./tsconfig.production.json",
"format": "prettier:fix && lint:fix", "format": "prettier:fix && lint:fix",
"lint": "eslint --ext .ts,.tsx src/", "lint": "eslint --ext .ts,.tsx src/",
@@ -20,23 +21,18 @@
"@babel/core": "^7.21.8", "@babel/core": "^7.21.8",
"@babel/preset-env": "^7.21.5", "@babel/preset-env": "^7.21.5",
"@babel/preset-typescript": "^7.21.5", "@babel/preset-typescript": "^7.21.5",
"@simplewebauthn/typescript-types": "^7.x.x",
"@tsconfig/node16": "^1.0.4", "@tsconfig/node16": "^1.0.4",
"@types/jest": "^29.5.1", "@types/jest": "^29.5.1",
"@types/jsonwebtoken": "^9.0.1", "@types/jsonwebtoken": "^9.0.1",
"@types/koa": "^2.13.5", "@types/koa": "^2.13.5",
"@types/koa-bodyparser": "^4.3.10", "@types/koa-bodyparser": "^4.3.10",
"@types/koa-cookie": "^1.0.0", "@types/koa-cookie": "^1.0.0",
"@types/koa-passport": "^4.0.3",
"@types/koa-router": "^7.4.4", "@types/koa-router": "^7.4.4",
"@types/koa-session": "^5.10.6", "@types/koa-session": "^5.10.6",
"@types/luxon": "^3.2.0", "@types/luxon": "^3.2.0",
"@types/node": "^18.14.0", "@types/node": "^18.14.0",
"@types/passport": "^1.0.12", "@types/uuid": "^9.0.1",
"@types/passport-facebook": "^2.1.11",
"@types/passport-fido2-webauthn": "^0.1.0",
"@types/passport-google-oauth": "^1.0.42",
"@types/passport-jwt": "^3.0.8",
"@types/passport-local": "^1.0.35",
"@typescript-eslint/eslint-plugin": "^5.59.2", "@typescript-eslint/eslint-plugin": "^5.59.2",
"@typescript-eslint/parser": "^5.59.2", "@typescript-eslint/parser": "^5.59.2",
"babel-jest": "^29.5.0", "babel-jest": "^29.5.0",
@@ -53,30 +49,27 @@
"prettier-eslint": "^15.0.1", "prettier-eslint": "^15.0.1",
"prettier-eslint-cli": "^7.1.0", "prettier-eslint-cli": "^7.1.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"ts-node": "^10.9.1", "ts-node": "^10.x.x",
"typescript": "^4.9.5" "typescript": "^4.x.x"
}, },
"dependencies": { "dependencies": {
"@mifi/auth-db": "^1.x.x", "@mifi/auth-common": "^1.0.15",
"@mifi/services-common": "^1.x.x", "@mifi/auth-db": "^1.0.20",
"@simplewebauthn/server": "^7.2.0", "@mifi/auth-gatekeeper": "^1.0.12",
"dotenv": "^16.0.3", "@mifi/services-common": "^1.0.16",
"http-status-codes": "^2.2.0", "@simplewebauthn/server": "^7.x.x",
"jsonwebtoken": "^9.0.0", "base64url": "^3.x.x",
"koa": "^2.14.1", "dotenv": "^16.x.x",
"koa-bodyparser": "^4.3.0", "http-status-codes": "^2.x.x",
"koa-cookie": "^1.0.0", "jsonwebtoken": "^9.x.x",
"koa-passport": "^6.0.0", "koa": "^2.x.x",
"koa-router": "^12.0.0", "koa-bodyparser": "^4.x.x",
"koa-session": "^6.4.0", "koa-compose": "^4.1.0",
"koa-cookie": "^1.x.x",
"koa-router": "^12.x.x",
"koa-session": "^6.x.x",
"luxon": "^3.3.0", "luxon": "^3.3.0",
"passport": "^0.6.0", "uuid": "^9.x.x"
"passport-facebook": "^3.0.0",
"passport-fido2-webauthn": "^0.1.0",
"passport-google-oauth": "^2.0.0",
"passport-http-bearer": "^1.0.1",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0"
}, },
"description": "", "description": "",
"repository": { "repository": {

View File

@@ -3,11 +3,12 @@ import bodyparser from 'koa-bodyparser';
import cookie from 'koa-cookie'; import cookie from 'koa-cookie';
import session from 'koa-session'; import session from 'koa-session';
import passport from './passport'; import { gatekeeper } from '@mifi/auth-gatekeeper/lib';
import { performanceLogger, performanceTimer } from './middleware/performance'; import { performanceLogger, performanceTimer } from './middleware/performance';
import { errorHandler } from './middleware/errorHandler'; import { errorHandler } from './middleware/errorHandler';
import { authRouter } from './controllers/auth'; import { authRouter } from './controllers/auth';
import { SESSION_KEY } from './constants/env'; import { SESSION_KEY } from './env';
const app: Koa = new Koa(); const app: Koa = new Koa();
@@ -20,8 +21,8 @@ app.use(cookie());
app.keys = [SESSION_KEY]; app.keys = [SESSION_KEY];
app.use(session({}, app)); app.use(session({}, app));
app.use(passport.initialize()); app.use(gatekeeper.initialize);
app.use(passport.session()); app.use(gatekeeper.jwt);
app.use(authRouter.routes()); app.use(authRouter.routes());
app.use(authRouter.allowedMethods()); app.use(authRouter.allowedMethods());

View File

@@ -1,9 +0,0 @@
export enum Action {
AUTHENTICATE = 'AUTHENTICATE',
AUTHENTICATE_FAILURE = 'AUTHENTICATE_FAILURE',
CREATE = 'CREATE',
DELETE = 'DELETE',
RESET = 'RESET',
RESET_REQUEST = 'RESET_REQUEST',
UPDATE = 'UPDATE',
}

View File

@@ -1,8 +0,0 @@
export enum Status {
ACTIVE,
BLOCK_HARD,
BLOCK_SOFT,
DELETED,
INACTIVE,
UNVERIFIED,
}

View File

@@ -1,10 +0,0 @@
export const DB_HOST = process.env.DB_HOST;
export const DB_PORT = process.env.DB_PORT || 27017;
export const DB_USERNAME = process.env.DB_USERNAME;
export const DB_PASSWORD = process.env.DB_PASSWORD;
export const DB_NAME = process.env.DB_NAME;
export const COLL_AUTH = 'Auth';
export const COLL_LOG = 'Log';
export const COLL_STRATEGY = 'Strategy';
export const COLL_TOKEN = 'Token';

View File

@@ -1,20 +0,0 @@
export const PACKAGE_NAME = '@mifi/auth';
export const PORT = process.env.PORT || 9000;
export const SESSION_KEY = process.env.SESSION_KEY || 'secret-key';
export const JWT_AUDIENCE = process.env.JWT_AUDIENCE || 'mifi.dev';
export const JWT_ISSUER = process.env.JWT_ISSUER || PACKAGE_NAME;
export const JWT_SECRET = process.env.JWT_SECRET || 'secret';
export const LOGIN_VALID_TIMEOUT = process.env.LOGIN_VALID_TIMEOUT || '12h'; // ###d|h|m
export const RESET_VALID_TIMEOUT = process.env.RESET_VALID_TIMEOUT || '15m'; // ###d|h|m
export const VERIFY_VALID_TIMEOUT = process.env.VERIFY_VALID_TIMEOUT || '60d'; // ###d|h|m
export const DEFAULT_TOKEN_DAYS = process.env.DEFAULT_TOKEN_DAYS || 365;
export const ROUTE_PREFIX = process.env.ROUTE_PREFIX || '/auth';
export const LOGIN_ROUTE = process.env.LOGIN_ROUTE || '/login';
export const RESET_ROUTE = process.env.RESET_ROUTE || '/reset';
export const VERIFICATION_ROUTE = process.env.VERIFICATION_ROUTE || '/verification';
export const REQUIRE_VERIFICATION = process.env.REQUIRE_VERIFICATION || true;

View File

@@ -1,7 +0,0 @@
export enum STRATEGIES {
LOCAL,
APPLE,
FACEBOOK,
FIDO2,
GOOGLE,
}

View File

@@ -1,4 +0,0 @@
export enum TokenType {
RESET = 'RESET',
VERIFICATION = 'VERIFICATION',
}

View File

@@ -3,6 +3,8 @@ import Koa from 'koa';
import Router from 'koa-router'; import Router from 'koa-router';
import { StringSchemaDefinition } from 'mongoose'; import { StringSchemaDefinition } from 'mongoose';
import { ROUTE_PREFIX as prefix, RESET_ROUTE } from '@mifi/auth-common/lib/env/routes';
import { STRATEGIES } from '@mifi/auth-common/lib/enums/strategies';
import { Auth } from '@mifi/auth-db/lib'; import { Auth } from '@mifi/auth-db/lib';
import { deleteStrategy } from '@mifi/auth-db/lib/api/deleteStrategy'; import { deleteStrategy } from '@mifi/auth-db/lib/api/deleteStrategy';
import { resetPasswordGet } from '@mifi/auth-db/lib/api/resetPasswordGet'; import { resetPasswordGet } from '@mifi/auth-db/lib/api/resetPasswordGet';
@@ -10,11 +12,9 @@ import { resetPasswordPost } from '@mifi/auth-db/lib/api/resetPasswordPost';
import { create } from '@mifi/auth-db/lib/dao/create'; import { create } from '@mifi/auth-db/lib/dao/create';
import { deleteById } from '@mifi/auth-db/lib/dao/deleteById'; import { deleteById } from '@mifi/auth-db/lib/dao/deleteById';
import { AuthDocument } from '@mifi/auth-db/lib/schema/auth'; import { AuthDocument } from '@mifi/auth-db/lib/schema/auth';
import { gatekeeper } from '@mifi/auth-gatekeeper/lib';
import { ROUTE_PREFIX as prefix, RESET_ROUTE } from '../constants/env';
import passport from '../passport';
import { ErrorCodes, getErrorBody } from '../constants/errors'; import { ErrorCodes, getErrorBody } from '../constants/errors';
import { authenticated } from '../middleware/authenication';
const routerOpts: Router.IRouterOptions = { prefix }; const routerOpts: Router.IRouterOptions = { prefix };
const router: Router = new Router(routerOpts); const router: Router = new Router(routerOpts);
@@ -34,20 +34,15 @@ router.post('/', async (ctx) => {
ctx.body = { success: !!data, data }; ctx.body = { success: !!data, data };
}); });
router.delete('/strategy/:id', async (ctx) => { router.delete('/strategy/:id', gatekeeper.authenticated, async (ctx) => {
ctx.body = { success: await deleteStrategy(ctx.params.id as StringSchemaDefinition) }; ctx.body = { success: await deleteStrategy(ctx.params.id as StringSchemaDefinition) };
}); });
router.delete('/:id', async (ctx) => { router.delete('/:id', gatekeeper.authenticated, async (ctx) => {
ctx.body = { success: await deleteById(ctx.params.id as StringSchemaDefinition) }; ctx.body = { success: await deleteById(ctx.params.id as StringSchemaDefinition) };
}); });
router.post('/login', async (ctx, next) => { router.post('/login', gatekeeper.authenticate(STRATEGIES[STRATEGIES.LOCAL]));
return passport.authenticate('local', (err, user) => {
ctx.body = user;
return user ? ctx.login(user) : ctx.throw(StatusCodes.UNAUTHORIZED);
})(ctx, next);
});
router.post(process.env.RESET_ROUTE || RESET_ROUTE, async (ctx) => { router.post(process.env.RESET_ROUTE || RESET_ROUTE, async (ctx) => {
const { password, token, username } = ctx.request.body as { token?: string; password?: string; username?: string }; const { password, token, username } = ctx.request.body as { token?: string; password?: string; username?: string };
@@ -66,8 +61,8 @@ router.post(process.env.RESET_ROUTE || RESET_ROUTE, async (ctx) => {
} }
}); });
router.patch('/:record', authenticated(), (ctx: Koa.Context) => { router.patch('/:record', gatekeeper.authenticated, (ctx: Koa.Context) => {
if (ctx.user !== ctx.param.record) { if (ctx.user.record !== ctx.param.record) {
ctx.throw(StatusCodes.UNAUTHORIZED); ctx.throw(StatusCodes.UNAUTHORIZED);
} }
const data = Auth.findOneAndUpdate({ record: ctx.params.record }); const data = Auth.findOneAndUpdate({ record: ctx.params.record });

View File

@@ -0,0 +1,10 @@
// import Router from 'koa-router';
// const routerOpts: Router.IRouterOptions = { prefix: '/passkey' };
// const router: Router = new Router(routerOpts);
// router.post('/login', async (ctx, next) => {});
// router.post('/login/challenge', async (ctx, next) => {});
// router.post('/signup/challenge', async (ctx, next) => {});

4
src/env.ts Normal file
View File

@@ -0,0 +1,4 @@
import { getEnvVar } from '@mifi/services-common/lib/utils/getEnvVar';
export const SESSION_KEY = getEnvVar<string>('SESSION_KEY', 'secret');
export const PORT = getEnvVar<number>('PORT', 9001);

View File

@@ -1,6 +1,6 @@
import app from './app'; import app from './app';
import { connection } from '@mifi/auth-db/lib'; import { connection } from '@mifi/auth-db/lib';
import { PORT } from './constants/env'; import { PORT } from './env';
connection.then( connection.then(
() => { () => {

View File

@@ -1,13 +0,0 @@
import { Middleware } from 'koa';
import { LOGIN_ROUTE } from '../constants/env';
export const authenticated = (): Middleware => {
return (ctx, next) => {
if (ctx.isAuthenticated()) {
return next();
} else {
ctx.redirect(process.env.LOGIN_ROUTE || LOGIN_ROUTE);
}
};
};

View File

@@ -1,7 +1,7 @@
import { StatusCodes } from 'http-status-codes'; import { StatusCodes } from 'http-status-codes';
import { Context, Next } from 'koa'; import { Middleware } from 'koa';
export const errorHandler = async (ctx: Context, next: Next) => { export const errorHandler: Middleware = async (ctx, next) => {
try { try {
await next(); await next();
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any

View File

@@ -1,12 +1,12 @@
import { Context, Next } from 'koa'; import { Middleware } from 'koa';
export const performanceLogger = async (ctx: Context, next: Next) => { export const performanceLogger: Middleware = async (ctx, next) => {
await next(); await next();
const rt = ctx.response.get('X-Response-Time'); const rt = ctx.response.get('X-Response-Time');
console.log(`${ctx.method} ${ctx.url} - ${rt}`); console.debug(`${ctx.method} ${ctx.url} - ${rt}`);
}; };
export const performanceTimer = async (ctx: Context, next: Next) => { export const performanceTimer: Middleware = async (ctx, next) => {
const start = Date.now(); const start = Date.now();
await next(); await next();
const ms = Date.now() - start; const ms = Date.now() - start;

View File

@@ -1,23 +0,0 @@
import passport from 'koa-passport';
import { Types } from 'mongoose';
import { AuthDocument } from '@mifi/auth-db/lib/schema/auth';
import { readOneByRecord } from '@mifi/auth-db/lib/dao/readOneByRecord';
import { readOneById } from '@mifi/auth-db/lib/dao/readOneById';
import LocalStrategy from './strategies/local';
import JwtStrategy from './strategies/jwt';
passport.use(LocalStrategy);
passport.use(JwtStrategy);
passport.serializeUser((user, done) => {
done(null, (user as AuthDocument).record || (user as AuthDocument).id);
});
passport.deserializeUser(async (id, done) => {
const user = await readOneByRecord(<Types.ObjectId>id).catch(async () => await readOneById(<Types.ObjectId>id));
done(user ? null : 'user not found', user);
});
export default passport;

View File

@@ -1,17 +0,0 @@
import { ExtractJwt, Strategy as JwtStrategy } from 'passport-jwt';
import { readOneByRecord } from '@mifi/auth-db/lib/dao/readOneByRecord';
import { JWT_SECRET } from '../../constants/env';
const opts = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: JWT_SECRET,
issuer: process.env.JWT_ISSUER,
audience: process.env.JWT_AUDIENCE,
};
export default new JwtStrategy(opts, async ({ sub }, done) => {
const auth = await readOneByRecord(sub);
return done(null, auth || false);
});

View File

@@ -1,9 +0,0 @@
// eslint-disable-next-line import/named
import { Strategy as LocalStrategy } from 'passport-local';
import { authenticate } from '@mifi/auth-db/lib/api/authenticate';
export default new LocalStrategy(async (username, password, done) => {
const user = await authenticate(username, password);
done(null, user);
});

View File

@@ -0,0 +1,18 @@
import { Context } from 'koa';
import { STRATEGIES } from '@mifi/auth-common/lib/enums/strategies';
import { authenticate } from '@mifi/auth-db/lib/api/authenticate';
import { Strategy } from '@mifi/auth-gatekeeper/lib';
import { GatekeeperUser } from '@mifi/auth-gatekeeper/lib/types/GatekeeperUser';
interface LocalStrategyBody {
username: string;
password: string;
}
const name = STRATEGIES[STRATEGIES.LOCAL];
export const LocalStrategy = new Strategy(name, async (ctx: Context) => {
const { password, username } = <LocalStrategyBody>ctx.request.body;
return authenticate(username, password) as Promise<false | GatekeeperUser>;
});

5
src/types/PasskeyUser.ts Normal file
View File

@@ -0,0 +1,5 @@
export interface PasskeyUser {
displayName: string;
id: Buffer | string;
name: string;
}

View File

@@ -1,35 +0,0 @@
import jwt from 'jsonwebtoken';
import { JWT_AUDIENCE, JWT_ISSUER, JWT_SECRET } from '../constants/env';
export interface TokenProps {
aud?: string;
exp?: number | Date;
iss?: string;
sub: string | null;
[key: string]: any;
}
export type SignProps = string | TokenProps | void;
export const sign = (props: SignProps) => {
const today = new Date();
const { sub = null, ...rest }: TokenProps =
typeof props === 'string' || typeof props === 'undefined' ? { sub: props || null } : props;
let { exp } = rest;
if (!exp) {
exp = new Date(today);
exp.setDate(today.getDate() + parseInt(process.env.JWT_DAYS_VALID as string));
exp = exp.getTime() / 1000;
}
return jwt.sign(
{
exp,
sub,
aud: rest.aud || JWT_AUDIENCE,
iat: today.getTime(),
iss: rest.iss || JWT_ISSUER,
},
JWT_SECRET,
);
};
export const verify = (token: string) => jwt.verify(token, JWT_SECRET);

View File

@@ -1,4 +1,4 @@
import { RESET_ROUTE, ROUTE_PREFIX, VERIFICATION_ROUTE } from '../constants/env'; import { RESET_ROUTE, ROUTE_PREFIX, VERIFICATION_ROUTE } from '@mifi/auth-common/lib/env/routes';
export const getPasswordResetPath = (token: string) => `${ROUTE_PREFIX}${RESET_ROUTE}?t=${token}`; export const getPasswordResetPath = (token: string) => `${ROUTE_PREFIX}${RESET_ROUTE}?t=${token}`;

View File

@@ -1,13 +0,0 @@
export const parseTimeoutToMs = (timeout: string) => {
const match = timeout.match(/(?<number>\d+)(?<unit>d|h|m)/gi)?.groups || {};
const { number, unit } = match;
switch (unit) {
case 'd':
return 1000 * 60 * 60 * 24 * parseInt(number);
case 'h':
return 1000 * 60 * 60 * parseInt(number);
case 'm':
default:
return 1000 * 60 * parseInt(number) || 1;
}
};

View File

@@ -1,12 +0,0 @@
import { pbkdf2Sync, randomBytes } from 'crypto';
export const encrypt = (password: string) => {
const salt = randomBytes(16).toString('hex');
const hash = pbkdf2Sync(password, salt, 10000, 512, 'sha512').toString('hex');
return `${salt}:${hash}`;
};
export const verify = (test: string, secret: string) => {
const [salt, hash] = secret.split(':');
return pbkdf2Sync(test, salt, 10000, 512, 'sha512').toString('hex') === hash;
};

View File

@@ -1,11 +0,0 @@
import { sign } from './jwt';
import { LOGIN_VALID_TIMEOUT } from '../constants/env';
import { Status } from '../constants/auth';
import { parseTimeoutToMs } from './parseTimeoutToMs';
export const generateLoginToken = (sub: string, status: Status) =>
sign({
sub,
status,
exp: Date.now() + parseTimeoutToMs(LOGIN_VALID_TIMEOUT),
});

17
tsconfig.package.json Normal file
View File

@@ -0,0 +1,17 @@
{
"extends": "@tsconfig/node16/tsconfig.json",
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"declaration": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"noImplicitAny": true,
"outDir": "dist/",
"rootDirs": ["./", "src/"],
"strict": true,
"esModuleInterop": true,
"sourceMap": true,
"removeComments": false
},
"include": ["./src"]
}

View File

@@ -2,7 +2,7 @@
"extends": "@tsconfig/node16/tsconfig.json", "extends": "@tsconfig/node16/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"declaration": true, "declaration": false,
"experimentalDecorators": true, "experimentalDecorators": true,
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"noImplicitAny": true, "noImplicitAny": true,

2469
yarn.lock

File diff suppressed because it is too large Load Diff