19 Commits
1.0.1 ... main

Author SHA1 Message Date
efb237885f Merge pull request 'Release 1.0.6' (#4) from develop into main
All checks were successful
continuous-integration/drone/tag Build is passing
Reviewed-on: #4
2023-05-26 21:49:25 +00:00
ec61e29fb0 Tweaks and mergefail fixes
All checks were successful
continuous-integration/drone/pr Build is passing
2023-05-26 17:46:37 -04:00
d97a7d7f95 Merge branch 'main' into develop
Some checks failed
continuous-integration/drone/pr Build is failing
# Conflicts:
#	.npmignore
#	lib/dao/deleteById.ts
#	lib/dao/readOneByRecord.ts
#	lib/dao/readOneByUsername.ts
#	package.json
#	src/dao/auth/deleteById.ts
#	src/dao/auth/readOneByRecord.ts
#	src/dao/auth/readOneByUsername.ts
#	src/dao/create.ts
#	src/dao/deleteById.ts
#	src/dao/readOneByRecord.ts
#	src/dao/readOneByUsername.ts
2023-05-26 17:44:52 -04:00
4bbe520366 Merge pull request 'Add DAO shit for Strategy model, added types for different ops, bump package version' (#3) from feature/ISS-3_-_WebAuthn_Support into develop
Some checks failed
continuous-integration/drone/pr Build is failing
Reviewed-on: #3
2023-05-26 21:40:07 +00:00
e657d09ae5 Add DAO shit for Strategy model, added types for different ops, bump package version
All checks were successful
continuous-integration/drone/pr Build is passing
2023-05-26 17:35:58 -04:00
08293e8c16 .npmignore updates 2023-05-24 13:08:49 -04:00
88562d01cd Restructuring the folders (#1)
Co-authored-by: mifi <badmf@mifi.dev>
Reviewed-on: #1
2023-05-24 16:48:47 +00:00
fdfd8b567a Last try before I force it...
All checks were successful
continuous-integration/drone/pr Build is passing
2023-05-24 12:44:01 -04:00
55c2274a4f Grrrr...
Some checks failed
continuous-integration/drone/pr Build is failing
2023-05-24 12:41:12 -04:00
7d40fdfe74 Bam!
Some checks failed
continuous-integration/drone/pr Build is failing
2023-05-24 12:39:37 -04:00
8e5092b943 Haha!
Some checks failed
continuous-integration/drone/pr Build is failing
2023-05-24 12:35:37 -04:00
35b89062e4 Prettier?
Some checks failed
continuous-integration/drone/pr Build is failing
2023-05-24 10:46:24 -04:00
65e30ba1c7 Prettier...
Some checks failed
continuous-integration/drone/pr Build is failing
2023-05-24 10:33:41 -04:00
40645f58e3 Oops... paths.
Some checks failed
continuous-integration/drone/pr Build is failing
2023-05-24 10:31:38 -04:00
7619899edc Restructuring the folders
Some checks failed
continuous-integration/drone/pr Build is failing
2023-05-24 10:24:15 -04:00
d3210b73c9 Fucked up paths 2023-05-23 22:12:01 -04:00
6951cd3218 - Fix package versioning 2023-05-23 22:05:27 -04:00
9393a8441a Added drone pipeline 2023-05-23 15:40:57 -04:00
b2af58532c Fixes, version bump 2023-05-23 15:03:42 -04:00
57 changed files with 1832 additions and 507 deletions

136
.drone.yml Normal file
View File

@@ -0,0 +1,136 @@
kind: pipeline
type: docker
name: Test Pipeline
workspace:
path: /drone/auth
steps:
- name: yarn install
image: node:20-bullseye-slim
environment:
YARN_VERSION: 3.5.0
commands:
- yarn set version stable
- yarn install
volumes:
- name: yarnrc
path: /drone/auth/.yarnrc.yml
- name: Code Style Checks
image: node:latest
commands:
- yarn prettier
- name: Lint
image: node:latest
commands:
- yarn lint
- name: Unit Tests
image: node:latest
commands:
- yarn test
- name: Send Test Status Notification
image: plugins/webhook
settings:
urls: https://lab.mifi.dev/hooks/9p65zpagctgkmndo8nwwm4199r
content_type: application/json
template: |
{
"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 }}] Code Quality Checks {{ build.status }} {{#success build.status}}:tada:{{else}}:poop:{{/success}}",
"username":"DroneBot"
}
when:
status:
- success
- failure
- name: Build
image: node:latest
commands:
- yarn build:production
- name: Send Build Status Notifications
image: plugins/webhook
settings:
urls: https://lab.mifi.dev/hooks/9p65zpagctgkmndo8nwwm4199r
content_type: application/json
template: |
{
"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 }}] Build package {{ build.status }} {{#success build.status}}:tada:{{else}}:poop:{{/success}}",
"username":"DroneBot"
}
when:
status:
- success
- failure
volumes:
- name: yarnrc
host:
path: /volume1/docker/.yarnrc.yml
trigger:
branch:
- main
- develop
event:
- pull_request
---
kind: pipeline
type: docker
name: Publish Pipeline
workspace:
path: /drone/auth
steps:
- name: Build
image: node:20-bullseye-slim
environment:
YARN_VERSION: 3.5.0
commands:
- yarn set version stable
- yarn install
- yarn build:production
volumes:
- name: yarnrc
path: /drone/auth/.yarnrc.yml
- name: Publish NPM
image: node:20-bullseye-slim
commands:
- yarn npm publish
volumes:
- name: npmrc
path: /drone/auth/.npmrc
- name: yarnrc
path: /drone/auth/.yarnrc.yml
- name: Report NPM Publish Status
image: plugins/webhook
settings:
urls: https://lab.mifi.dev/hooks/ccw34hdf7tgbjmzp96nptn938r
content_type: application/json
template: |
{
"icon_url":"https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/120/apple/198/freezing-face_1f976.png",
"text": "[{{ repo.name }} - New npm package release {{tag}} from # {{ build.number }}] Deploy {{ build.status }} {{#success build.status}}:tada:{{else}}:poop:{{/success}}",
"username":"DroneBot"
}
when:
status:
- success
- failure
volumes:
- name: npmrc
host:
path: /volume1/docker/beethoven/labs-auth/.npmrc
- name: yarnrc
host:
path: /volume1/docker/.yarnrc.yml
depends_on:
- Test Pipeline
trigger:
event:
- tag

19
.eslintrc.js Normal file
View File

@@ -0,0 +1,19 @@
/* eslint-env node */
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/errors',
'plugin:prettier/recommended',
'prettier',
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
settings: {
'import/parsers': {
'@typescript-eslint/parser': ['.ts', '.tsx'],
},
'import/resolver': 'typescript',
},
root: true,
};

3
.gitignore vendored
View File

@@ -130,3 +130,6 @@ dist
.yarn/install-state.gz
.pnp.*
# Project
lib/

11
.npmignore Normal file
View File

@@ -0,0 +1,11 @@
.build.yarnrc.yml
.drone.yml
.eslintrc*
.npmrc
.prettierrc*
.yarnrc.yml
babel.config.*
jest.config.*
src
tsconfig*.json
tslint.json

7
.prettierrc.js Normal file
View File

@@ -0,0 +1,7 @@
module.exports = {
parser: 'typescript',
printWidth: 120,
trailingComma: 'all',
tabWidth: 4,
singleQuote: true,
};

873
.yarn/releases/yarn-3.5.1.cjs vendored Executable file

File diff suppressed because one or more lines are too long

View File

@@ -1 +1,3 @@
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.5.1.cjs

View File

@@ -1,37 +0,0 @@
import { DatabaseError } from '@mifi/services-common/lib/domain/errors/DatabaseError';
import { Auth, Log, Strategy, Token } from '..';
import { Auth as AuthProps } from '../schema/auth';
import { STRATEGIES } from '../constants/strategies';
import { REQUIRE_VERIFICATION } from '../constants/env';
import { TokenType } from '../constants/tokens';
import { Status } from '../constants/auth';
import { Action } from '../constants/action';
export const create = async ({ record, username, password }: AuthProps & { password: string }) => {
const status = REQUIRE_VERIFICATION ? Status.UNVERIFIED : Status.ACTIVE;
const doc = await Auth.create({
record,
status,
username,
}).catch((err) => {
throw new DatabaseError('failed to create user', { err });
});
if (doc) {
const strategy = await Strategy.create({ method: STRATEGIES.LOCAL, key: password, parent: doc._id }).catch(
(err) => {
throw new DatabaseError('failed to create strategy', { err });
},
);
if (strategy) {
doc.strategies.push(strategy._id);
await doc.save();
Log.add(doc._id, Action.CREATE);
return { doc, token: REQUIRE_VERIFICATION && (await Token.getToken(TokenType.VERIFICATION, doc._id)) };
}
await doc.deleteOne((err) => {
throw new DatabaseError('failed to remove invalid auth record', { err, doc });
});
}
return null;
};

View File

@@ -1,23 +1,23 @@
{
"name": "@mifi/auth-db",
"version": "1.0.1",
"version": "1.0.6",
"author": "mifi (Mike Fitzpatrick)",
"license": "MIT",
"scripts": {
"build": "tsc",
"build:production": "tsc -p .",
"build:production": "tsc -p ./tsconfig.production.json",
"format": "prettier:fix && lint:fix",
"lint": "eslint --ext .ts,.tsx lib/",
"lint:fix": "eslint --fix --ext .ts,.tsx lib/",
"prettier": "prettier --check 'lib/**/*.ts'",
"prettier:fix": "prettier --write 'lib/**/*.ts'",
"lint": "eslint --ext .ts,.tsx src/",
"lint:fix": "eslint --fix --ext .ts,.tsx src/",
"prettier": "prettier --check 'src/**/*.ts'",
"prettier:fix": "prettier --write 'src/**/*.ts'",
"test": "jest --passWithNoTests"
},
"devDependencies": {
"@babel/core": "^7.21.8",
"@babel/preset-env": "^7.21.5",
"@babel/preset-typescript": "^7.21.5",
"@tsconfig/node16": "^1.0.3",
"@tsconfig/node16": "^1.0.4",
"@types/jsonwebtoken": "^9.0.2",
"@types/node": "^18.14.0",
"@typescript-eslint/eslint-plugin": "^5.59.2",
@@ -35,10 +35,11 @@
"prettier-eslint": "^15.0.1",
"prettier-eslint-cli": "^7.1.0",
"reflect-metadata": "^0.1.13",
"ts-node": "^10.9.1",
"typescript": "^4.9.5"
},
"dependencies": {
"@mifi/services-common": "1.0.0",
"@mifi/services-common": "1.x.x",
"dotenv": "^16.0.3",
"jsonwebtoken": "^9.0.0",
"mongoose": "^6.9.2"
@@ -47,5 +48,6 @@
"repository": {
"type": "git",
"url": "https://git.mifi.dev/mifi/auth-db.git"
}
},
"packageManager": "yarn@3.5.1"
}

View File

@@ -1,5 +1,5 @@
import { Auth, Log } from '..';
import { Action } from '../../constants/action';
import { Action } from '../constants/action';
import { getLoginToken } from '../utils/getLoginToken';
export const authenticate = async (username: string, password: string) => {

View File

@@ -1,7 +1,7 @@
import { readOneByUsername } from '../dao/readOneByUsername';
import { Log, Token } from '..';
import { TokenType } from '../../constants/tokens';
import { Action } from '../../constants/action';
import { TokenType } from '../constants/tokens';
import { Action } from '../constants/action';
export const resetPasswordGet = async (username: string) => {
const doc = await readOneByUsername(username);

View File

@@ -1,18 +1,21 @@
import { Types } from 'mongoose';
import { Log, Strategy, Token } from '..';
import { STRATEGIES } from '../../constants/strategies';
import { STRATEGIES } from '../constants/strategies';
import { AuthDocument } from '../schema/auth';
import { getLoginToken } from '../utils/getLoginToken';
import { StrategyDocument } from '../schema/strategy';
import { Action } from '../../constants/action';
import { Action } from '../constants/action';
export const resetPasswordPost = async (token: string, password: string) => {
const parentId = await Token.validateResetToken(token);
if (parentId) {
let parent: AuthDocument;
let strategy: StrategyDocument | null = await Strategy.findOne({ parent: parentId, method: STRATEGIES.LOCAL });
let strategy: StrategyDocument | null = await Strategy.findOne({
parent: parentId,
method: STRATEGIES.LOCAL,
});
if (strategy) {
parent = await strategy.getAuthRecord();

62
src/dao/auth/create.ts Normal file
View File

@@ -0,0 +1,62 @@
import { DatabaseError } from '@mifi/services-common/lib/domain/errors/DatabaseError';
import { Auth, Log, Strategy, Token } from '../..';
import { Auth as AuthProps } from '../../schema/auth';
import { STRATEGIES } from '../../constants/strategies';
import { REQUIRE_VERIFICATION } from '../../constants/env';
import { TokenType } from '../../constants/tokens';
import { Status } from '../../constants/auth';
import { Action } from '../../constants/action';
type CreateProps = Pick<AuthProps, 'record' | 'username'> & {
externalId?: string;
password?: string;
publicKey?: string;
};
export const create = async ({ record, username, externalId, password, publicKey }: CreateProps) => {
const status = REQUIRE_VERIFICATION ? Status.UNVERIFIED : Status.ACTIVE;
const doc = await Auth.create({
record,
status,
username,
}).catch((err) => {
throw new DatabaseError('failed to create user', { err });
});
if (doc) {
const method = externalId && publicKey ? STRATEGIES.FIDO2 : STRATEGIES.LOCAL;
const strategy = await Strategy.create({
externalId,
key: password || publicKey,
method,
parent: doc._id,
}).catch((err) => {
throw new DatabaseError(`failed to create strategy ${STRATEGIES[method]}`, { err });
});
if (strategy) {
doc.strategies.push(strategy._id);
await doc.save();
Log.add(doc._id, Action.CREATE);
return {
doc,
token:
method === STRATEGIES.LOCAL &&
REQUIRE_VERIFICATION &&
(await Token.getToken(TokenType.VERIFICATION, doc._id)),
};
}
await doc.deleteOne((err) => {
throw new DatabaseError('failed to remove invalid auth record', {
err,
doc,
});
});
}
return null;
};
export type Fido2UserProps = Pick<AuthProps, 'record' | 'username'> & { externalId: string; publicKey: string };
export const createFido2User = (props: Fido2UserProps) => create(props);
export type LocalUserProps = Pick<AuthProps, 'record' | 'username'> & { password: string };
export const createLocalUser = (props: LocalUserProps) => create(props);

View File

@@ -1,11 +1,16 @@
import { StringSchemaDefinition } from 'mongoose';
import { Auth, Log, Strategy, Token } from '..';
import { Status } from '../constants/auth';
import { Action } from '../constants/action';
import { Auth, Log, Strategy, Token } from '../..';
import { Status } from '../../constants/auth';
import { Action } from '../../constants/action';
export const deleteById = async (id: StringSchemaDefinition) => {
if (await Auth.findByIdAndUpdate(id, { status: Status.DELETED, strategies: [] }).catch()) {
if (
await Auth.findByIdAndUpdate(id, {
status: Status.DELETED,
strategies: [],
}).catch()
) {
await Strategy.deleteMany({ parent: id });
await Token.deleteMany({ auth: id });
Log.add(id, Action.DELETE);

View File

@@ -1,8 +1,8 @@
import { FilterQuery } from 'mongoose';
import { Auth } from '..';
import { Status } from '../constants/auth';
import { AuthDocument } from '../schema/auth';
import { Auth } from '../../model/auth';
import { Status } from '../../constants/auth';
import { AuthDocument } from '../../schema/auth';
export const readAll = async (query: FilterQuery<AuthDocument> = {}) => Auth.find(query);

View File

@@ -1,5 +1,5 @@
import { Types } from 'mongoose';
import { Auth } from '..';
import { Auth } from '../../model/auth';
export const readOneById = async (id: Types.ObjectId) => Auth.findById(id);

View File

@@ -1,5 +1,5 @@
import { Types } from 'mongoose';
import { Auth } from '..';
import { Auth } from '../../model/auth';
export const readOneByRecord = async (record: Types.ObjectId) => Auth.findOne({ record });

View File

@@ -1,3 +1,3 @@
import { Auth } from '..';
import { Auth } from '../../model/auth';
export const readOneByUsername = async (username: string) => Auth.findOne({ username });

3
src/dao/create.ts Normal file
View File

@@ -0,0 +1,3 @@
import { create } from './auth/create';
export { create };

3
src/dao/deleteById.ts Normal file
View File

@@ -0,0 +1,3 @@
import { deleteById } from './auth/deleteById';
export { deleteById };

3
src/dao/readAll.ts Normal file
View File

@@ -0,0 +1,3 @@
import { readAll } from './auth/readAll';
export { readAll };

3
src/dao/readOneById.ts Normal file
View File

@@ -0,0 +1,3 @@
import { readOneById } from './auth/readOneById';
export { readOneById };

View File

@@ -0,0 +1,3 @@
import { readOneByRecord } from './auth/readOneByRecord';
export { readOneByRecord };

View File

@@ -0,0 +1,3 @@
import { readOneByUsername } from './auth/readOneByUsername';
export { readOneByUsername };

View File

@@ -0,0 +1,24 @@
import { DatabaseError } from '@mifi/services-common/lib/domain/errors/DatabaseError';
import { Strategy } from '../../model/strategy';
import { Strategy as StrategyProps } from '../../schema/strategy';
const create = async ({ externalId, key, method, parent, profile }: StrategyProps) =>
Strategy.create({
externalId,
key,
method,
parent,
profile,
}).catch((err) => {
throw new DatabaseError(err, {
externalId: externalId || null,
key,
method,
parent,
profile: profile || null,
});
});
export const createFido2Strategy = (props: Omit<StrategyProps, 'profile'>) => create(props);
export const createLocalStrategy = (props: Omit<StrategyProps, 'externalId' | 'profile'>) => create(props);

View File

@@ -0,0 +1,8 @@
import { StringSchemaDefinition } from 'mongoose';
import { Strategy } from '../../model/strategy';
export const deleteById = async (id: StringSchemaDefinition) => {
const result = await Strategy.findByIdAndDelete(id).catch();
return !!result;
};

View File

@@ -0,0 +1,6 @@
import { FilterQuery } from 'mongoose';
import { Strategy } from '../../model/strategy';
import { StrategyDocument } from '../../schema/strategy';
export const readAll = async (query: FilterQuery<StrategyDocument> = {}) => Strategy.find(query);

View File

@@ -0,0 +1,3 @@
import { Strategy } from '../../model/strategy';
export const readOneByExternalId = async (externalId: string) => Strategy.findOne({ externalId });

View File

@@ -0,0 +1,5 @@
import { Types } from 'mongoose';
import { Strategy } from '../../model/strategy';
export const readOneById = async (id: Types.ObjectId) => Strategy.findById(id);

View File

@@ -0,0 +1,7 @@
import { Types } from 'mongoose';
import { STRATEGIES } from '../../constants/strategies';
import { Strategy } from '../../model/strategy';
export const readOneByParentAndMethod = async (parent: Types.ObjectId, method: STRATEGIES) =>
Strategy.findOne({ method, parent });

View File

@@ -1,6 +1,6 @@
import mongoose from 'mongoose';
import { DB_HOST, DB_NAME, DB_PASSWORD, DB_PORT, DB_USERNAME } from '../constants/db';
import { DB_HOST, DB_NAME, DB_PASSWORD, DB_PORT, DB_USERNAME } from './constants/db';
import { Auth } from './model/auth';
import { Log } from './model/log';
import { Strategy } from './model/strategy';

View File

@@ -37,7 +37,12 @@ export const AuthSchema = new Schema<AuthDocument, AuthModel>(
{
is2FA: { type: Boolean, default: false },
record: { type: Types.ObjectId, unique: true },
status: { type: Number, enum: Status, default: Status.UNVERIFIED, index: true },
status: {
type: Number,
enum: Status,
default: Status.UNVERIFIED,
index: true,
},
strategies: [{ type: Types.ObjectId, ref: COLL_STRATEGY, default: [] }],
username: { type: String, required: true, unique: true },
},

View File

@@ -30,7 +30,9 @@ export const TokenSchema = new Schema<Token, TokenModel>(
TokenSchema.statics = {
async cleanupExpiredTokens() {
const { acknowledged, deletedCount } = await this.deleteMany({ expires: { $lte: Date.now() } });
const { acknowledged, deletedCount } = await this.deleteMany({
expires: { $lte: Date.now() },
});
return { success: acknowledged, deletedCount };
},
@@ -40,7 +42,11 @@ TokenSchema.statics = {
await existing.deleteOne();
}
const doc = await this.create({ type, auth, expires: expires || getDefaultExpiresFor(type) });
const doc = await this.create({
type,
auth,
expires: expires || getDefaultExpiresFor(type),
});
return sign({
sub: `${doc._id}`,
exp: doc.expires,

View File

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

17
tsconfig.production.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": "lib/",
"rootDirs": ["./", "src/"],
"strict": true,
"esModuleInterop": true,
"sourceMap": false,
"removeComments": true
},
"include": ["src"]
}

1009
yarn.lock

File diff suppressed because it is too large Load Diff