Guest User

Untitled

a guest
Nov 10th, 2017
130
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.05 KB | None | 0 0
  1. /* @flow */
  2. /* eslint-disable func-names */
  3.  
  4. import { Schema } from 'mongoose';
  5. import DB from 'schema/db';
  6. import composeWithMongoose from 'graphql-compose-mongoose';
  7. import composeWithRelay from 'graphql-compose-relay';
  8. import crypto from 'crypto';
  9. import bcrypt from 'bcrypt';
  10. import type { $Request } from 'express';
  11. import { AvatarUrlSchema, type AvatarUrlDoc } from 'schema/customTypes/avatarUrl';
  12. import { Cabinet, type CabinetDoc } from 'schema/cabinet';
  13.  
  14. export const UserSchema = new Schema(
  15. {
  16. email: {
  17. type: String,
  18. set: v => v.toLowerCase().trim(),
  19. // validate: (v) => UserSchema.isValidEmail(v), TODO
  20. required: true,
  21. },
  22. provider: {
  23. type: String,
  24. required: true,
  25. },
  26. providerId: {
  27. type: String,
  28. set: v => v.toLowerCase().trim(),
  29. description: 'String code of provider',
  30. required: true,
  31. },
  32. token: {
  33. type: String,
  34. required: true,
  35. default() {
  36. // if token not exists, generate random password
  37. return this.encryptPassword(Math.random().toString());
  38. },
  39. },
  40.  
  41. name: String,
  42. avatarUrl: AvatarUrlSchema,
  43. oneTimeTokenExp: String,
  44. meta: {
  45. type: Schema.Types.Mixed,
  46. },
  47. lastIp: String,
  48. lastLoginAt: Date,
  49. },
  50. {
  51. setDefaultsOnInsert: true,
  52. timestamps: true,
  53. collection: 'user',
  54. }
  55. );
  56.  
  57. UserSchema.index({ email: 1 }, { background: true });
  58. UserSchema.index({ provider: 1, providerId: 1 }, { unique: true, background: true });
  59.  
  60. export class UserDoc /* :: extends Mongoose$Document */ {
  61. email: string;
  62. provider: string;
  63. providerId: string;
  64. token: string;
  65. name: ?string;
  66. avatarUrl: ?(AvatarUrlDoc | $Shape<AvatarUrlDoc>);
  67. oneTimeTokenExp: ?string;
  68. meta: ?any;
  69. lastIp: ?string;
  70. lastLoginAt: ?Date;
  71. _plainPassword: ?string; // internal field, exists only on object create
  72. _oneTimeToken: ?string; // internal field
  73.  
  74. /** @deprecated remove when will be unused in other places of codebase */
  75. static async findByProviderDeprecated(provider, providerId, cb) {
  76. return this.findOne({ provider, providerId: providerId.toLowerCase().trim() }, cb);
  77. }
  78.  
  79. static isValidEmail(email) {
  80. 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
  81. return r.test(email);
  82. }
  83.  
  84. static async findByProvider(provider, providerId): Promise<?UserDoc> {
  85. return this.findOne({
  86. provider,
  87. providerId: providerId.toLowerCase().trim(),
  88. }).exec();
  89. }
  90.  
  91. static async findByEmail(email): Promise<?UserDoc> {
  92. return this.findOne({ email });
  93. }
  94.  
  95. static async findByEmailOrCreate({ email, password, provider, providerId }): Promise<UserDoc> {
  96. const existsUser = await this.findOne({ email });
  97. if (existsUser) return existsUser;
  98.  
  99. const newUser = new this({ email, password, provider, providerId });
  100.  
  101. await newUser.save();
  102. return newUser;
  103. }
  104.  
  105. set password(password: string) {
  106. this._plainPassword = password;
  107. this.token = this.encryptPassword(password);
  108. }
  109.  
  110. get password(): ?string {
  111. return this._plainPassword;
  112. }
  113.  
  114. encryptPassword(password: string): string {
  115. return bcrypt.hashSync(password, 10);
  116. }
  117.  
  118. checkPassword(password: string): boolean {
  119. if (this.token) {
  120. return bcrypt.compareSync(password, this.token);
  121. }
  122. return false;
  123. }
  124.  
  125. genPassword(len: number = 8): string {
  126. const newPass = crypto
  127. .randomBytes(Math.ceil(len * 3 / 4)) // eslint-disable-line
  128. .toString('base64') // convert to base64 format
  129. .slice(0, len) // return required number of characters
  130. .replace(/\+/g, '0') // replace '+' with '0'
  131. .replace(/\//g, '0'); // replace '/' with '0'
  132. this.password = newPass;
  133. return newPass;
  134. }
  135.  
  136. // by default valid 1 hour
  137. oneTimeTokenGenerate(EXPIRED_AFTER?: number = 3600000) {
  138. let token;
  139.  
  140. // If current one-time-token is valid at least half of expire period, then return existed
  141. // The reason is that user may require several emails in short period of time
  142. // So we should return same token, cause user may click by link from any email.
  143. if (this.oneTimeTokenExp) {
  144. const [tokenInDB, expireAtInDB] = this.oneTimeTokenExp.split(':');
  145. const expireAtInDBInt = parseInt(expireAtInDB, 10);
  146. if (expireAtInDBInt > Date.now() + EXPIRED_AFTER / 2) {
  147. token = tokenInDB;
  148. }
  149. }
  150.  
  151. // Generate new token
  152. if (!token) {
  153. token = crypto.randomBytes(20).toString('hex');
  154. const expireAt = Date.now() + EXPIRED_AFTER;
  155. this.oneTimeTokenExp = `${token}:${expireAt}`;
  156. }
  157.  
  158. this._oneTimeToken = token;
  159. return token;
  160. }
  161.  
  162. oneTimeTokenCheck(token: string): boolean {
  163. if (!token || !this.oneTimeTokenExp) return false;
  164. const [tokenInDB, expireAt] = this.oneTimeTokenExp.split(':');
  165. return parseInt(expireAt, 10) > Date.now() && token === tokenInDB;
  166. }
  167.  
  168. oneTimeTokenRevoke(): void {
  169. this.oneTimeTokenExp = null;
  170. }
  171.  
  172. async oneTimeLoginGenerate(EXPIRED_AFTER: number): Promise<string> {
  173. this.oneTimeTokenRevoke();
  174. const token = this.oneTimeTokenGenerate(EXPIRED_AFTER);
  175. await this.save();
  176.  
  177. const email64 = Buffer.from(this.email).toString('base64');
  178. return `${email64}:${token}`;
  179. }
  180.  
  181. touchLastLoginAt(req: $Request): this {
  182. let ip = '';
  183. if (req) {
  184. ip =
  185. (req.headers && req.headers['x-forwarded-for']) ||
  186. (req.connection && req.connection.remoteAddress);
  187. }
  188. this.lastIp = ip;
  189. this.lastLoginAt = new Date();
  190. return this;
  191. }
  192.  
  193. async getCabinets(all: boolean = false): Promise<Array<CabinetDoc>> {
  194. if (!this.email) return Promise.resolve([]);
  195.  
  196. if (all) {
  197. return Cabinet.find({
  198. $or: [{ users: this.email }, { owner: this.email }],
  199. }).exec();
  200. }
  201.  
  202. return Cabinet.find({ owner: this.email }).exec();
  203. }
  204. }
  205.  
  206. UserSchema.loadClass(UserDoc);
  207.  
  208. export const User = DB.data.model('User', UserSchema);
  209.  
  210. export const UserTC = composeWithRelay(composeWithMongoose(User));
  211.  
  212. UserTC.getResolver('createOne')
  213. .getArgTC('input')
  214. .getFieldTC('record')
  215. .addFields({
  216. password: 'String!',
  217. });
Add Comment
Please, Sign In to add comment