UNPKG

@cocalc/server

Version:

CoCalc server functionality: functions used by either the hub and the next.js server

198 lines (179 loc) 8.06 kB
/* * This file is part of CoCalc: Copyright © 2022 Sagemath, Inc. * License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details */ import { AuthenticateOptions } from "passport"; import { Router } from "express"; import { PostgreSQL } from "@cocalc/database/postgres/types"; export interface InitPassport { router: Router; database: PostgreSQL; host: string; cb: (err?) => void; } // see @hub/sign-in interface RecordSignInOpts { ip_address: string; successful: boolean; database: PostgreSQL; email_address?: string; account_id?: string; remember_me: boolean; } export interface PassportLoginOpts { passports: { [k: string]: PassportStrategyDB }; database: PostgreSQL; strategyName: string; record_sign_in: (opts: RecordSignInOpts) => void; // a function of that old "hub/sign-in" module profile: any; // complex object id: string; // id is required. e.g. take the email address – see create_passport in postgres-server-queries.coffee first_name?: string; last_name?: string; full_name?: string; emails?: string[]; req: any; res: any; update_on_login: boolean; // passed down from StrategyConf, default false cookie_ttl_s?: number; // how long the remember_me cookied lasts (default is a month or so) host: string; cb?: (err) => void; } export interface PassportManagerOpts { router: Router; database: PostgreSQL; host: string; } // passport_login state export interface PassportLoginLocals { account_id: string | undefined; email_address: string | undefined; new_account_created: boolean; has_valid_remember_me: boolean; target: string; cookies: any; remember_me_cookie: string; get_api_key: string; action: "regenerate" | "get" | undefined; api_key: string | undefined; } // maps the full profile object to a string or list of strings (e.g. "first_name") export type LoginInfoDerivator<T> = (profile: any) => T; export interface StrategyConf { name: string; // our custom name type: PassportTypes; // e.g. "saml" PassportStrategyConstructor: any; extra_opts?: { enableProof?: boolean; // facebook profileFields?: string[]; // facebook includeEmail?: boolean; // twitter }; auth_opts?: AuthenticateOptions; // return type has to partially fit with passport_login login_info: { id: string | LoginInfoDerivator<string>; // id is required! first_name?: string | LoginInfoDerivator<string>; last_name?: string | LoginInfoDerivator<string>; full_name?: string | LoginInfoDerivator<string>; emails?: string | LoginInfoDerivator<string[]>; }; userinfoURL?: string; // OAuth2, to get a profile update_on_login?: boolean; // if true, update the user's profile on login cookie_ttl_s?: number; // how long the remember_me cookied lasts (default is a month or so) } export type LoginInfoKeys = "id" | "first_name" | "last_name" | "emails"; // google, facebook, etc ... are not included, they're hardcoded export const PassportTypesList = [ "email", // special case, always included by default, not a passport strategy "activedirectory", "apple", "azuread", "gitlab2", "oauth1", "oauth2", "oauth2next", "oidc", "orcid", "saml", // the 4 types for google, twitter, github and facebook are not included here – they're hardcoded special cases ] as const; export type PassportTypes = typeof PassportTypesList[number]; // the OAuth2 strategies export function isOAuth2(type: PassportTypes): boolean { return type === "oauth2" || type === "oauth2next"; } export type PassportLoginInfo = { [key in LoginInfoKeys]?: string }; /** * To confgure a passport strategy, the "type" field is required. * It associates these config parameters with a strategy constructor from one of the passport.js strategies. * The remaining fields, except for type, clientID, clientSecret, and callbackURL, userinfoURL, login_info are passed to that constructor. * Additionally, there are default values for some of the fields, e.g. for the SAML2.0 strategy. * Please check the hub/auth.ts file for more details. * Regarding the userinfoURL, this is used by OAuth2 to get the profile. * The "login_info" field is a mapping from "cocalc" profile fields, that end up in the DB, * to the entries in the generated profile object. The DB entry can only be a string and * processing is done by using the "dot-object" npm library. * What should be provided is a mapping like that (the default for OAuth2), which in particular provides a unique id (a number or email address): * { * id: "id", * first_name: "name.givenName", * last_name: "name.familyName", * emails: "emails[0].value", * } */ export interface PassportStrategyDBConfig { type: PassportTypes; clientID?: string; // Google, Twitter, ... and OAuth2 clientSecret?: string; // Google, Twitter, ... and OAuth2 authorizationURL?: string; // OAuth2 tokenURL?: string; // --*-- userinfoURL?: string; // OAuth2, to get a profile login_info?: PassportLoginInfo; // extracting fields from the returned profile, uses "dot-object", e.g. { emails: "emails[0].value" } auth_opts?: { [key: string]: string }; // auth options, typed as AuthenticateOptions but OAuth2 has one which isn't part of the type – hence we keep it general } /** * The "info" column contains information, which is relevant to CoCalc's side of SSO strategies. * - public (default true): if false, the strategy is not shown prominently, but moved to the dedicated /sso/... pages. * Set this to false for all "institutional" SSO connections. (public would be Google, Twitter, etc. where anyone can have an account) * - do_not_hide: if public is false and do_not_hide is true, the strategy is still shown prominently. * - exclusive_domains: a list of domain extensions, matching also subdomains, e.g. ["example.com", "example.org"] * would match foo@example.com and bar@baz.example.org * The ultimate intention is that users with such email addresses have to go through that authentication mechanism. * They're also prevented from linking with other passports, changing email address, or unlinking that passport from their account. * That way, the organization behind that SSO mechanism has full control over the user's account. * - display: The string that's presented to the user as the name of that SSO strategy. * - description: A longer description of the strategy, could be markdown, shown on the dedicated /sso/... pages. * - icon: A URL to an icon * - disabled: if true, this is ignored during the initialization * - update_on_login: if true, the user's profile is updated on login (first and last name, not email) * - cookie_ttl_s: how long the remember_me cookied lasts (default is a month or so). * This could be set to a much shorter period to force users more frequently to re-login. */ export interface PassportStrategyDBInfo { public?: boolean; // default true do_not_hide?: boolean; // default false, only relevant for public=false SSOs, which will be shown on the login/signup page directly exclusive_domains?: string[]; // list of domains, e.g. ["foo.com"], which must go through that SSO mechanism (and hence block normal email signup) display?: string; // e.g. "WOW Tech", fallback: capitalize(strategy) description?: string; // markdown icon?: string; // URL to a square image disabled?: boolean; // if true, ignore this entry. default false. update_on_login?: boolean; // if true, update the user's info on login. default false. cookie_ttl_s?: number; // default is about a month } // those are the 3 columns in the DB table export interface PassportStrategyDB { strategy: string; // must be unique conf: PassportStrategyDBConfig; info?: PassportStrategyDBInfo; } export interface StrategyInstanceOpts { type: PassportTypes; opts: { [key: string]: any }; userinfoURL: string | undefined; PassportStrategyConstructor: new (options, verify) => any; } export interface UserProfileCallbackOpts { strategy_instance: any; userinfoURL: string; L2: Function; type: PassportTypes; }