This commit is contained in:
4
lib/db/model/auth.ts
Normal file
4
lib/db/model/auth.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import mongoose from 'mongoose';
|
||||
import { AuthModel, AuthPrivate, AuthSchema } from '../schema/auth';
|
||||
|
||||
export default mongoose.model<AuthPrivate, AuthModel>('Auth', AuthSchema);
|
||||
146
lib/db/schema/auth.ts
Normal file
146
lib/db/schema/auth.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { JwtPayload } from 'jsonwebtoken';
|
||||
import { InferSchemaType, Model, Schema, StringSchemaDefinition, Types } from 'mongoose';
|
||||
|
||||
import { Strategy } from './strategy';
|
||||
import { STRATEGIES } from '../../constants/strategies';
|
||||
import { TokenProps, verify as verifyJwt } from '../../utils/jwt';
|
||||
import { encrypt, verify as verifyPassword } from '../../utils/password';
|
||||
import { generateLoginToken, generateResetToken } from '../../utils/tokens';
|
||||
import { getPasswordResetLink } from '../../utils/links';
|
||||
import { Status } from '../../constants/auth';
|
||||
|
||||
export type Auth = {
|
||||
is2FA?: boolean;
|
||||
record: StringSchemaDefinition;
|
||||
username: string;
|
||||
};
|
||||
|
||||
export type AuthPrivate = Auth & {
|
||||
status: Status;
|
||||
strategies: Types.ArraySubdocument<Strategy>;
|
||||
};
|
||||
|
||||
export interface AuthMethods {
|
||||
authenticate(password: string): boolean;
|
||||
getAuthStrategy(method?: STRATEGIES): Strategy | false;
|
||||
getResetLink(route: string): Promise<string | undefined>;
|
||||
getResetToken(): Promise<string | undefined>;
|
||||
getToken(props?: Omit<TokenProps, 'sub'> | void): string;
|
||||
isActive(): boolean;
|
||||
setPassword(password: string): Promise<boolean>;
|
||||
}
|
||||
|
||||
export interface AuthModel extends Model<AuthPrivate, void, AuthMethods> {
|
||||
authenticate(username: string, password?: string): string | false;
|
||||
findByUsername(username: string): Promise<AuthModel & AuthPrivate>;
|
||||
isUsernameAvailable(username: string): Promise<boolean>;
|
||||
findUserForReset(strategy: STRATEGIES, token: string): Promise<Strategy | undefined>;
|
||||
resetPassword(token: string, password: string): Promise<string | false>;
|
||||
}
|
||||
|
||||
export const AuthSchema = new Schema<AuthPrivate, AuthModel, AuthMethods>(
|
||||
{
|
||||
is2FA: { type: Boolean, default: false },
|
||||
record: { type: Types.ObjectId },
|
||||
strategies: { type: Types.ArraySubdocument<Strategy>, required: true },
|
||||
status: { type: Number, enum: Object.values(Status), default: Status.UNVERIFIED },
|
||||
username: { type: String, required: true, unique: true },
|
||||
},
|
||||
{
|
||||
minimize: true,
|
||||
timestamps: true,
|
||||
},
|
||||
);
|
||||
|
||||
AuthSchema.methods = {
|
||||
authenticate(password: string) {
|
||||
const strategy = this.getAuthStrategy(STRATEGIES.LOCAL);
|
||||
return !!strategy && verifyPassword(password, strategy.key);
|
||||
},
|
||||
|
||||
getAuthStrategy(method = STRATEGIES.LOCAL) {
|
||||
return this.strategies.filter((strategy: Strategy) => strategy.method === method).pop() || false;
|
||||
},
|
||||
|
||||
getToken(props = {}) {
|
||||
return generateLoginToken(this._id, this.status);
|
||||
},
|
||||
|
||||
async getResetLink(route) {
|
||||
const token = await this.getResetToken();
|
||||
if (token) {
|
||||
const resetUrl = getPasswordResetLink(token);
|
||||
console.log('[sendPasswordReset] resetUrl:', resetUrl);
|
||||
return resetUrl;
|
||||
}
|
||||
},
|
||||
|
||||
async getResetToken() {
|
||||
const { key, token } = generateResetToken(this._id);
|
||||
this.resetCheckBit = key;
|
||||
await this.save().catch(() => undefined);
|
||||
return token;
|
||||
},
|
||||
|
||||
isActive() {
|
||||
return this.status === Status.ACTIVE;
|
||||
},
|
||||
|
||||
async setPassword(password) {
|
||||
const key = encrypt(password);
|
||||
const hasLocalStrategy = !!this.getAuthStrategy(STRATEGIES.LOCAL);
|
||||
const strategy = {
|
||||
key,
|
||||
method: STRATEGIES.LOCAL,
|
||||
resetToken: undefined,
|
||||
};
|
||||
|
||||
if (hasLocalStrategy) {
|
||||
await this.model('User')
|
||||
.findOneAndUpdate(
|
||||
{ _id: this._id, 'strategies.method': STRATEGIES.LOCAL },
|
||||
{ $set: { 'strategies.$': strategy } },
|
||||
{ upsert: true },
|
||||
)
|
||||
.catch();
|
||||
return true;
|
||||
}
|
||||
this.credentials.push(strategy);
|
||||
await this.save().catch(() => false);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
AuthSchema.statics = {
|
||||
authenticate: async function (username, password) {
|
||||
const auth = await this.findByUsername(username);
|
||||
if (auth && auth.authenticate(password)) {
|
||||
return auth.record;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
async findByUsername(username) {
|
||||
return this.findOne({ username });
|
||||
},
|
||||
|
||||
async isUsernameAvailable(username) {
|
||||
return !this.findByUsername(username);
|
||||
},
|
||||
|
||||
async resetPassword(token, password) {
|
||||
const decoded = verifyJwt(token);
|
||||
const { sub, key } = decoded as JwtPayload;
|
||||
const auth = await this.findOne({
|
||||
_id: sub,
|
||||
'strategies.resetToken': key,
|
||||
}).catch();
|
||||
if (auth) {
|
||||
await auth.setPassword(password).catch();
|
||||
return auth.getToken();
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
export type AuthSchema = InferSchemaType<typeof AuthSchema>;
|
||||
25
lib/db/schema/strategy.ts
Normal file
25
lib/db/schema/strategy.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { InferSchemaType, Schema, Types } from 'mongoose';
|
||||
import { STRATEGIES } from '../../constants/strategies';
|
||||
|
||||
export const Strategy = new Schema(
|
||||
{
|
||||
method: {
|
||||
type: Number,
|
||||
enum: Object.values(STRATEGIES),
|
||||
index: true,
|
||||
required: true,
|
||||
unique: true,
|
||||
},
|
||||
externalId: { type: String, index: true },
|
||||
key: { type: String, required: true, trim: true },
|
||||
profile: {},
|
||||
resetToken: { type: String },
|
||||
forceReset: { type: Boolean },
|
||||
},
|
||||
{
|
||||
minimize: true,
|
||||
timestamps: true,
|
||||
},
|
||||
);
|
||||
|
||||
export type Strategy = InferSchemaType<typeof Strategy>;
|
||||
Reference in New Issue
Block a user