diff --git a/package.json b/package.json index 94e1f80..c54a2b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mifi/auth-db", - "version": "1.0.5", + "version": "1.0.6", "author": "mifi (Mike Fitzpatrick)", "license": "MIT", "scripts": { diff --git a/src/dao/auth/create.ts b/src/dao/auth/create.ts new file mode 100644 index 0000000..e20b3d5 --- /dev/null +++ b/src/dao/auth/create.ts @@ -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 & { + 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 & { externalId: string; publicKey: string }; +export const createFido2User = (props: Fido2UserProps) => create(props); + +export type LocalUserProps = Pick & { password: string }; +export const createLocalUser = (props: LocalUserProps) => create(props); diff --git a/src/dao/auth/deleteById.ts b/src/dao/auth/deleteById.ts new file mode 100644 index 0000000..ae8fbbf --- /dev/null +++ b/src/dao/auth/deleteById.ts @@ -0,0 +1,20 @@ +import { StringSchemaDefinition } from 'mongoose'; + +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() + ) { + await Strategy.deleteMany({ parent: id }); + await Token.deleteMany({ auth: id }); + Log.add(id, Action.DELETE); + return true; + } + return false; +}; diff --git a/src/dao/auth/readAll.ts b/src/dao/auth/readAll.ts new file mode 100644 index 0000000..2661b0a --- /dev/null +++ b/src/dao/auth/readAll.ts @@ -0,0 +1,9 @@ +import { FilterQuery } from 'mongoose'; + +import { Auth } from '../../model/auth'; +import { Status } from '../../constants/auth'; +import { AuthDocument } from '../../schema/auth'; + +export const readAll = async (query: FilterQuery = {}) => Auth.find(query); + +export const readAllActive = async () => readAll({ status: { $ne: Status.DELETED } }); diff --git a/src/dao/auth/readOneById.ts b/src/dao/auth/readOneById.ts new file mode 100644 index 0000000..bfcb5dd --- /dev/null +++ b/src/dao/auth/readOneById.ts @@ -0,0 +1,5 @@ +import { Types } from 'mongoose'; + +import { Auth } from '../../model/auth'; + +export const readOneById = async (id: Types.ObjectId) => Auth.findById(id); diff --git a/src/dao/auth/readOneByRecord.ts b/src/dao/auth/readOneByRecord.ts new file mode 100644 index 0000000..ba5649b --- /dev/null +++ b/src/dao/auth/readOneByRecord.ts @@ -0,0 +1,5 @@ +import { Types } from 'mongoose'; + +import { Auth } from '../../model/auth'; + +export const readOneByRecord = async (record: Types.ObjectId) => Auth.findOne({ record }); diff --git a/src/dao/auth/readOneByUsername.ts b/src/dao/auth/readOneByUsername.ts new file mode 100644 index 0000000..380f05b --- /dev/null +++ b/src/dao/auth/readOneByUsername.ts @@ -0,0 +1,3 @@ +import { Auth } from '../../model/auth'; + +export const readOneByUsername = async (username: string) => Auth.findOne({ username }); diff --git a/src/dao/create.ts b/src/dao/create.ts index a88f336..65da7ce 100644 --- a/src/dao/create.ts +++ b/src/dao/create.ts @@ -1,45 +1,3 @@ -import { DatabaseError } from '@mifi/services-common/lib/domain/errors/DatabaseError'; +import { create } from './auth/create'; -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; -}; +export { create }; diff --git a/src/dao/deleteById.ts b/src/dao/deleteById.ts index a8fd485..f401f2e 100644 --- a/src/dao/deleteById.ts +++ b/src/dao/deleteById.ts @@ -1,20 +1,3 @@ -import { StringSchemaDefinition } from 'mongoose'; +import { deleteById } from './auth/deleteById'; -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() - ) { - await Strategy.deleteMany({ parent: id }); - await Token.deleteMany({ auth: id }); - Log.add(id, Action.DELETE); - return true; - } - return false; -}; +export { deleteById }; diff --git a/src/dao/readOneByRecord.ts b/src/dao/readOneByRecord.ts index 70ff0cb..3b10f35 100644 --- a/src/dao/readOneByRecord.ts +++ b/src/dao/readOneByRecord.ts @@ -1,5 +1,3 @@ -import { Types } from 'mongoose'; +import { readOneByRecord } from './auth/readOneByRecord'; -import { Auth } from '../model/auth'; - -export const readOneByRecord = async (record: Types.ObjectId) => Auth.findOne({ record }); +export { readOneByRecord }; diff --git a/src/dao/readOneByUsername.ts b/src/dao/readOneByUsername.ts index d6e4945..22e24d6 100644 --- a/src/dao/readOneByUsername.ts +++ b/src/dao/readOneByUsername.ts @@ -1,3 +1,3 @@ -import { Auth } from '../model/auth'; +import { readOneByUsername } from './auth/readOneByUsername'; -export const readOneByUsername = async (username: string) => Auth.findOne({ username }); +export { readOneByUsername }; diff --git a/src/dao/strategy/create.ts b/src/dao/strategy/create.ts new file mode 100644 index 0000000..1f4dfcf --- /dev/null +++ b/src/dao/strategy/create.ts @@ -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) => create(props); +export const createLocalStrategy = (props: Omit) => create(props); diff --git a/src/dao/strategy/deleteById.ts b/src/dao/strategy/deleteById.ts new file mode 100644 index 0000000..9747a94 --- /dev/null +++ b/src/dao/strategy/deleteById.ts @@ -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; +}; diff --git a/src/dao/strategy/readAll.ts b/src/dao/strategy/readAll.ts new file mode 100644 index 0000000..c7c1cfe --- /dev/null +++ b/src/dao/strategy/readAll.ts @@ -0,0 +1,6 @@ +import { FilterQuery } from 'mongoose'; + +import { Strategy } from '../../model/strategy'; +import { StrategyDocument } from '../../schema/strategy'; + +export const readAll = async (query: FilterQuery = {}) => Strategy.find(query); diff --git a/src/dao/strategy/readOneByExternalId.ts b/src/dao/strategy/readOneByExternalId.ts new file mode 100644 index 0000000..0fc751a --- /dev/null +++ b/src/dao/strategy/readOneByExternalId.ts @@ -0,0 +1,3 @@ +import { Strategy } from '../../model/strategy'; + +export const readOneByExternalId = async (externalId: string) => Strategy.findOne({ externalId }); diff --git a/src/dao/strategy/readOneById.ts b/src/dao/strategy/readOneById.ts new file mode 100644 index 0000000..722b257 --- /dev/null +++ b/src/dao/strategy/readOneById.ts @@ -0,0 +1,5 @@ +import { Types } from 'mongoose'; + +import { Strategy } from '../../model/strategy'; + +export const readOneById = async (id: Types.ObjectId) => Strategy.findById(id); diff --git a/src/dao/strategy/readOneByParentAndMethod.ts b/src/dao/strategy/readOneByParentAndMethod.ts new file mode 100644 index 0000000..a33787f --- /dev/null +++ b/src/dao/strategy/readOneByParentAndMethod.ts @@ -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 });