Files
outline/server/migrations/20250331231413-add-oauth-server-models.js
T
Tom Moor a06671e8ce OAuth provider (#8884)
This PR contains the necessary work to make Outline an OAuth provider including:

- OAuth app registration
- OAuth app management
- Private / public apps (Public in cloud only)
- Full OAuth 2.0 spec compatible authentication flow
- Granular scopes
- User token management screen in settings
- Associated API endpoints for programatic access
2025-05-03 19:40:18 -04:00

213 lines
5.1 KiB
JavaScript

"use strict";
/** @type {import("sequelize-cli").Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.sequelize.transaction(async (transaction) => {
await queryInterface.createTable("oauth_clients", {
id: {
type: Sequelize.UUID,
primaryKey: true,
allowNull: false
},
name: {
type: Sequelize.STRING,
allowNull: false
},
description: {
type: Sequelize.STRING,
allowNull: true
},
developerName: {
type: Sequelize.STRING,
allowNull: true
},
developerUrl: {
type: Sequelize.STRING,
allowNull: true
},
avatarUrl: {
type: Sequelize.STRING,
allowNull: true
},
clientId: {
type: Sequelize.STRING,
allowNull: false,
unique: true,
},
clientSecret: {
type: Sequelize.BLOB,
allowNull: false
},
published: {
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: false
},
teamId: {
type: Sequelize.UUID,
references: {
model: "teams",
},
allowNull: false,
onDelete: "cascade"
},
createdById: {
type: Sequelize.UUID,
references: {
model: "users",
},
allowNull: false
},
redirectUris: {
type: Sequelize.ARRAY(Sequelize.STRING),
allowNull: false,
defaultValue: []
},
createdAt: {
type: Sequelize.DATE,
allowNull: false
},
updatedAt: {
type: Sequelize.DATE,
allowNull: false
},
deletedAt: {
type: Sequelize.DATE,
allowNull: true
}
}, {
transaction
});
await queryInterface.createTable("oauth_authorization_codes", {
id: {
type: Sequelize.UUID,
primaryKey: true,
allowNull: false
},
authorizationCodeHash: {
type: Sequelize.STRING,
allowNull: false
},
codeChallenge: {
type: Sequelize.STRING,
allowNull: true
},
codeChallengeMethod: {
type: Sequelize.STRING,
allowNull: true
},
scope: {
type: Sequelize.ARRAY(Sequelize.STRING),
allowNull: false
},
oauthClientId: {
type: Sequelize.UUID,
references: {
model: "oauth_clients",
},
onDelete: "cascade",
allowNull: false
},
userId: {
type: Sequelize.UUID,
references: {
model: "users",
},
onDelete: "cascade",
allowNull: false
},
redirectUri: {
type: Sequelize.STRING,
allowNull: false
},
expiresAt: {
type: Sequelize.DATE,
allowNull: false
},
createdAt: {
type: Sequelize.DATE,
allowNull: false
}
}, {
transaction
});
await queryInterface.createTable("oauth_authentications", {
id: {
type: Sequelize.UUID,
primaryKey: true,
allowNull: false
},
accessTokenHash: {
type: Sequelize.STRING,
allowNull: false,
unique: true
},
accessTokenExpiresAt: {
type: Sequelize.DATE,
allowNull: false
},
refreshTokenHash: {
type: Sequelize.STRING,
allowNull: false,
unique: true
},
refreshTokenExpiresAt: {
type: Sequelize.DATE,
allowNull: false
},
lastActiveAt: {
type: Sequelize.DATE,
allowNull: true
},
scope: {
type: Sequelize.ARRAY(Sequelize.STRING),
allowNull: false
},
oauthClientId: {
type: Sequelize.UUID,
references: {
model: "oauth_clients",
},
onDelete: "cascade",
allowNull: false
},
userId: {
type: Sequelize.UUID,
references: {
model: "users",
},
onDelete: "cascade",
allowNull: false
},
createdAt: {
type: Sequelize.DATE,
allowNull: false
},
updatedAt: {
type: Sequelize.DATE,
allowNull: false
},
deletedAt: {
type: Sequelize.DATE,
allowNull: true
}
}, {
transaction
});
await queryInterface.addIndex("oauth_clients", ["teamId"], { transaction });
});
},
async down(queryInterface, Sequelize) {
await queryInterface.sequelize.transaction(async (transaction) => {
await queryInterface.dropTable("oauth_authentications", { transaction });
await queryInterface.dropTable("oauth_authorization_codes", { transaction });
await queryInterface.dropTable("oauth_clients", { transaction });
});
}
};