chore: Migrate authentication to new tables (#1929)

This work provides a foundation for a more pluggable authentication system such as the one outlined in #1317.

closes #1317
This commit is contained in:
Tom Moor
2021-03-09 12:22:08 -08:00
committed by GitHub
parent ab7b16bbb9
commit ed2a42ac27
35 changed files with 1280 additions and 297 deletions

View File

@@ -0,0 +1,51 @@
// @flow
import fs from "fs";
import path from "path";
import { DataTypes, sequelize } from "../sequelize";
// Each authentication provider must have a definition under server/auth, the
// name of the file will be used as reference in the db, one less thing to config
const authProviders = fs
.readdirSync(path.resolve(__dirname, "..", "auth"))
.filter(
(file) =>
file.indexOf(".") !== 0 &&
!file.includes(".test") &&
!file.includes("index.js")
)
.map((fileName) => fileName.replace(".js", ""));
const AuthenticationProvider = sequelize.define(
"authentication_providers",
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
validate: {
isIn: [authProviders],
},
},
enabled: {
type: DataTypes.BOOLEAN,
defaultValue: true,
},
providerId: {
type: DataTypes.STRING,
},
},
{
timestamps: true,
updatedAt: false,
}
);
AuthenticationProvider.associate = (models) => {
AuthenticationProvider.belongsTo(models.Team);
AuthenticationProvider.hasMany(models.UserAuthentication);
};
export default AuthenticationProvider;

View File

@@ -249,9 +249,7 @@ describe("#membershipUserIds", () => {
const users = await Promise.all(
Array(6)
.fill()
.map(() => {
return buildUser({ teamId });
})
.map(() => buildUser({ teamId }))
);
const collection = await buildCollection({

View File

@@ -96,6 +96,14 @@ Team.associate = (models) => {
Team.hasMany(models.Collection, { as: "collections" });
Team.hasMany(models.Document, { as: "documents" });
Team.hasMany(models.User, { as: "users" });
Team.hasMany(models.AuthenticationProvider, {
as: "authenticationProviders",
});
Team.addScope("withAuthenticationProviders", {
include: [
{ model: models.AuthenticationProvider, as: "authenticationProviders" },
],
});
};
const uploadAvatar = async (model) => {
@@ -121,13 +129,13 @@ const uploadAvatar = async (model) => {
}
};
Team.prototype.provisionSubdomain = async function (subdomain) {
Team.prototype.provisionSubdomain = async function (subdomain, options = {}) {
if (this.subdomain) return this.subdomain;
let append = 0;
while (true) {
try {
await this.update({ subdomain });
await this.update({ subdomain }, options);
break;
} catch (err) {
// subdomain was invalid or already used, try again

View File

@@ -79,7 +79,12 @@ User.associate = (models) => {
});
User.hasMany(models.Document, { as: "documents" });
User.hasMany(models.View, { as: "views" });
User.hasMany(models.UserAuthentication, { as: "authentications" });
User.belongsTo(models.Team);
User.addScope("withAuthentications", {
include: [{ model: models.UserAuthentication, as: "authentications" }],
});
};
// Instance methods
@@ -151,10 +156,6 @@ User.prototype.getTransferToken = function () {
// Returns a temporary token that is only used for logging in from an email
// It can only be used to sign in once and has a medium length expiry
User.prototype.getEmailSigninToken = function () {
if (this.service && this.service !== "email") {
throw new Error("Cannot generate email signin token for OAuth user");
}
return JWT.sign(
{ id: this.id, createdAt: new Date().toISOString(), type: "email-signin" },
this.jwtSecret

View File

@@ -0,0 +1,24 @@
// @flow
import { DataTypes, sequelize, encryptedFields } from "../sequelize";
const UserAuthentication = sequelize.define("user_authentications", {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
scopes: DataTypes.ARRAY(DataTypes.STRING),
accessToken: encryptedFields().vault("accessToken"),
refreshToken: encryptedFields().vault("refreshToken"),
providerId: {
type: DataTypes.STRING,
unique: true,
},
});
UserAuthentication.associate = (models) => {
UserAuthentication.belongsTo(models.AuthenticationProvider);
UserAuthentication.belongsTo(models.User);
};
export default UserAuthentication;

View File

@@ -2,6 +2,7 @@
import ApiKey from "./ApiKey";
import Attachment from "./Attachment";
import Authentication from "./Authentication";
import AuthenticationProvider from "./AuthenticationProvider";
import Backlink from "./Backlink";
import Collection from "./Collection";
import CollectionGroup from "./CollectionGroup";
@@ -19,12 +20,14 @@ import Share from "./Share";
import Star from "./Star";
import Team from "./Team";
import User from "./User";
import UserAuthentication from "./UserAuthentication";
import View from "./View";
const models = {
ApiKey,
Attachment,
Authentication,
AuthenticationProvider,
Backlink,
Collection,
CollectionGroup,
@@ -42,6 +45,7 @@ const models = {
Star,
Team,
User,
UserAuthentication,
View,
};
@@ -56,6 +60,7 @@ export {
ApiKey,
Attachment,
Authentication,
AuthenticationProvider,
Backlink,
Collection,
CollectionGroup,
@@ -73,5 +78,6 @@ export {
Star,
Team,
User,
UserAuthentication,
View,
};