mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
a06671e8ce
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
114 lines
3.5 KiB
TypeScript
114 lines
3.5 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
import find from "lodash/find";
|
|
import { Scope } from "@shared/types";
|
|
import env from "@server/env";
|
|
import Team from "@server/models/Team";
|
|
import { Hook, PluginManager } from "@server/utils/PluginManager";
|
|
|
|
export default class AuthenticationHelper {
|
|
/**
|
|
* The mapping of method names to their scopes, anything not listed here
|
|
* defaults to `Scope.Write`.
|
|
*
|
|
* - `documents.create` -> `Scope.Create`
|
|
* - `documents.list` -> `Scope.Read`
|
|
* - `documents.info` -> `Scope.Read`
|
|
*/
|
|
private static methodToScope = {
|
|
create: Scope.Create,
|
|
list: Scope.Read,
|
|
info: Scope.Read,
|
|
search: Scope.Read,
|
|
documents: Scope.Read,
|
|
};
|
|
|
|
/**
|
|
* Returns the enabled authentication provider configurations for the current
|
|
* installation.
|
|
*
|
|
* @returns A list of authentication providers
|
|
*/
|
|
public static get providers() {
|
|
return PluginManager.getHooks(Hook.AuthProvider);
|
|
}
|
|
|
|
/**
|
|
* Returns the enabled authentication provider configurations for a team,
|
|
* if given otherwise all enabled providers are returned.
|
|
*
|
|
* @param team The team to get enabled providers for
|
|
* @returns A list of authentication providers
|
|
*/
|
|
public static providersForTeam(team?: Team) {
|
|
const isCloudHosted = env.isCloudHosted;
|
|
|
|
return AuthenticationHelper.providers
|
|
.sort((hook) => (hook.value.id === "email" ? 1 : -1))
|
|
.filter((hook) => {
|
|
// Email sign-in is an exception as it does not have an authentication
|
|
// provider using passport, instead it exists as a boolean option.
|
|
if (hook.value.id === "email") {
|
|
return team?.emailSigninEnabled;
|
|
}
|
|
|
|
// If no team return all possible authentication providers except email.
|
|
if (!team) {
|
|
return true;
|
|
}
|
|
|
|
const authProvider = find(team.authenticationProviders, {
|
|
name: hook.value.id,
|
|
});
|
|
|
|
// If cloud hosted then the auth provider must be enabled for the team,
|
|
// If self-hosted then it must not be actively disabled, otherwise all
|
|
// providers are considered.
|
|
return (
|
|
(!isCloudHosted && authProvider?.enabled !== false) ||
|
|
(isCloudHosted && authProvider?.enabled)
|
|
);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns whether the given path can be accessed with any of the scopes. We
|
|
* support scopes in the formats of:
|
|
*
|
|
* - `/api/namespace.method`
|
|
* - `namespace:scope`
|
|
* - `scope`
|
|
*
|
|
* @param path The path to check
|
|
* @param scopes The scopes to check
|
|
* @returns True if the path can be accessed
|
|
*/
|
|
public static canAccess = (path: string, scopes: string[]) => {
|
|
// strip any query string, this is never used as part of scope matching
|
|
path = path.split("?")[0];
|
|
|
|
const resource = path.split("/").pop() ?? "";
|
|
const [namespace, method] = resource.split(".");
|
|
|
|
return scopes.some((scope) => {
|
|
const [scopeNamespace, scopeMethod] = scope.match(/[:\.]/g)
|
|
? scope.replace("/api/", "").split(/[:\.]/g)
|
|
: ["*", scope];
|
|
const isRouteScope = scope.startsWith("/api/");
|
|
|
|
if (isRouteScope) {
|
|
return (
|
|
(namespace === scopeNamespace || scopeNamespace === "*") &&
|
|
(method === scopeMethod || scopeMethod === "*")
|
|
);
|
|
}
|
|
|
|
return (
|
|
(namespace === scopeNamespace || scopeNamespace === "*") &&
|
|
(scopeMethod === Scope.Write ||
|
|
this.methodToScope[method as keyof typeof this.methodToScope] ===
|
|
scopeMethod)
|
|
);
|
|
});
|
|
};
|
|
}
|