Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /* @flow */
- /* eslint-disable func-names */
- import { Schema } from 'mongoose';
- import DB from 'schema/db';
- import composeWithMongoose from 'graphql-compose-mongoose';
- import composeWithRelay from 'graphql-compose-relay';
- import crypto from 'crypto';
- import bcrypt from 'bcrypt';
- import type { $Request } from 'express';
- import { AvatarUrlSchema, type AvatarUrlDoc } from 'schema/customTypes/avatarUrl';
- import { Cabinet, type CabinetDoc } from 'schema/cabinet';
- export const UserSchema = new Schema(
- {
- email: {
- type: String,
- set: v => v.toLowerCase().trim(),
- // validate: (v) => UserSchema.isValidEmail(v), TODO
- required: true,
- },
- provider: {
- type: String,
- required: true,
- },
- providerId: {
- type: String,
- set: v => v.toLowerCase().trim(),
- description: 'String code of provider',
- required: true,
- },
- token: {
- type: String,
- required: true,
- default() {
- // if token not exists, generate random password
- return this.encryptPassword(Math.random().toString());
- },
- },
- name: String,
- avatarUrl: AvatarUrlSchema,
- oneTimeTokenExp: String,
- meta: {
- type: Schema.Types.Mixed,
- },
- lastIp: String,
- lastLoginAt: Date,
- },
- {
- setDefaultsOnInsert: true,
- timestamps: true,
- collection: 'user',
- }
- );
- UserSchema.index({ email: 1 }, { background: true });
- UserSchema.index({ provider: 1, providerId: 1 }, { unique: true, background: true });
- export class UserDoc /* :: extends Mongoose$Document */ {
- email: string;
- provider: string;
- providerId: string;
- token: string;
- name: ?string;
- avatarUrl: ?(AvatarUrlDoc | $Shape<AvatarUrlDoc>);
- oneTimeTokenExp: ?string;
- meta: ?any;
- lastIp: ?string;
- lastLoginAt: ?Date;
- _plainPassword: ?string; // internal field, exists only on object create
- _oneTimeToken: ?string; // internal field
- /** @deprecated remove when will be unused in other places of codebase */
- static async findByProviderDeprecated(provider, providerId, cb) {
- return this.findOne({ provider, providerId: providerId.toLowerCase().trim() }, cb);
- }
- static isValidEmail(email) {
- const r = /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/; // eslint-disable-line
- return r.test(email);
- }
- static async findByProvider(provider, providerId): Promise<?UserDoc> {
- return this.findOne({
- provider,
- providerId: providerId.toLowerCase().trim(),
- }).exec();
- }
- static async findByEmail(email): Promise<?UserDoc> {
- return this.findOne({ email });
- }
- static async findByEmailOrCreate({ email, password, provider, providerId }): Promise<UserDoc> {
- const existsUser = await this.findOne({ email });
- if (existsUser) return existsUser;
- const newUser = new this({ email, password, provider, providerId });
- await newUser.save();
- return newUser;
- }
- set password(password: string) {
- this._plainPassword = password;
- this.token = this.encryptPassword(password);
- }
- get password(): ?string {
- return this._plainPassword;
- }
- encryptPassword(password: string): string {
- return bcrypt.hashSync(password, 10);
- }
- checkPassword(password: string): boolean {
- if (this.token) {
- return bcrypt.compareSync(password, this.token);
- }
- return false;
- }
- genPassword(len: number = 8): string {
- const newPass = crypto
- .randomBytes(Math.ceil(len * 3 / 4)) // eslint-disable-line
- .toString('base64') // convert to base64 format
- .slice(0, len) // return required number of characters
- .replace(/\+/g, '0') // replace '+' with '0'
- .replace(/\//g, '0'); // replace '/' with '0'
- this.password = newPass;
- return newPass;
- }
- // by default valid 1 hour
- oneTimeTokenGenerate(EXPIRED_AFTER?: number = 3600000) {
- let token;
- // If current one-time-token is valid at least half of expire period, then return existed
- // The reason is that user may require several emails in short period of time
- // So we should return same token, cause user may click by link from any email.
- if (this.oneTimeTokenExp) {
- const [tokenInDB, expireAtInDB] = this.oneTimeTokenExp.split(':');
- const expireAtInDBInt = parseInt(expireAtInDB, 10);
- if (expireAtInDBInt > Date.now() + EXPIRED_AFTER / 2) {
- token = tokenInDB;
- }
- }
- // Generate new token
- if (!token) {
- token = crypto.randomBytes(20).toString('hex');
- const expireAt = Date.now() + EXPIRED_AFTER;
- this.oneTimeTokenExp = `${token}:${expireAt}`;
- }
- this._oneTimeToken = token;
- return token;
- }
- oneTimeTokenCheck(token: string): boolean {
- if (!token || !this.oneTimeTokenExp) return false;
- const [tokenInDB, expireAt] = this.oneTimeTokenExp.split(':');
- return parseInt(expireAt, 10) > Date.now() && token === tokenInDB;
- }
- oneTimeTokenRevoke(): void {
- this.oneTimeTokenExp = null;
- }
- async oneTimeLoginGenerate(EXPIRED_AFTER: number): Promise<string> {
- this.oneTimeTokenRevoke();
- const token = this.oneTimeTokenGenerate(EXPIRED_AFTER);
- await this.save();
- const email64 = Buffer.from(this.email).toString('base64');
- return `${email64}:${token}`;
- }
- touchLastLoginAt(req: $Request): this {
- let ip = '';
- if (req) {
- ip =
- (req.headers && req.headers['x-forwarded-for']) ||
- (req.connection && req.connection.remoteAddress);
- }
- this.lastIp = ip;
- this.lastLoginAt = new Date();
- return this;
- }
- async getCabinets(all: boolean = false): Promise<Array<CabinetDoc>> {
- if (!this.email) return Promise.resolve([]);
- if (all) {
- return Cabinet.find({
- $or: [{ users: this.email }, { owner: this.email }],
- }).exec();
- }
- return Cabinet.find({ owner: this.email }).exec();
- }
- }
- UserSchema.loadClass(UserDoc);
- export const User = DB.data.model('User', UserSchema);
- export const UserTC = composeWithRelay(composeWithMongoose(User));
- UserTC.getResolver('createOne')
- .getArgTC('input')
- .getFieldTC('record')
- .addFields({
- password: 'String!',
- });
Add Comment
Please, Sign In to add comment