1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-11-19 00:26:27 +03:00

syn2mas: use zod to validate the config files (#1938)

This commit is contained in:
Quentin Gliech
2023-10-16 14:56:01 +02:00
committed by GitHub
parent 3bcf4b2500
commit a6093b615e
28 changed files with 2981 additions and 1706 deletions

View File

@@ -1,22 +1,77 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const HEADER_TEMPLATE = `\
// Copyright %%CURRENT_YEAR%% The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
`;
/** @type {import('eslint').Linter.Config} */
module.exports = { module.exports = {
plugins: [ root: true,
'matrix-org', plugins: ["matrix-org"],
],
extends: [ extends: [
'plugin:matrix-org/typescript', "plugin:prettier/recommended",
"plugin:import/recommended",
"plugin:import/typescript",
"plugin:matrix-org/typescript",
], ],
env: { env: {
browser: false, browser: false,
node: true, node: true,
}, },
parser: '@typescript-eslint/parser', parser: "@typescript-eslint/parser",
parserOptions: { parserOptions: {
project: "./tsconfig.json", project: "./tsconfig.eslint.json",
}, },
rules: { rules: {
'@typescript-eslint/no-floating-promises': 'error', "matrix-org/require-copyright-header": ["error", HEADER_TEMPLATE],
'@typescript-eslint/no-misused-promises': 'error', "import/order": [
'@typescript-eslint/promise-function-async': 'error', "error",
'@typescript-eslint/await-thenable': 'error', {
"newlines-between": "always",
alphabetize: { order: "asc" },
}, },
],
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/promise-function-async": "error",
"@typescript-eslint/await-thenable": "error",
// False-positive because of id128 and log4js
"import/no-named-as-default-member": "off",
},
settings: {
"import/parsers": {
"@typescript-eslint/parser": [".ts", ".mts"],
},
"import/resolver": {
typescript: true,
node: true,
},
},
ignorePatterns: ["dist"],
}; };

File diff suppressed because it is too large Load Diff

View File

@@ -8,11 +8,13 @@
"build": "tsc", "build": "tsc",
"dev": "ts-node --esm src/index.ts", "dev": "ts-node --esm src/index.ts",
"lint": "npm run lint:types && npm run lint:style", "lint": "npm run lint:types && npm run lint:style",
"lint:style": "eslint src", "lint:style": "eslint . .eslintrc.cjs",
"lint:types": "tsc --noEmit --skipLibCheck", "lint:types": "tsc --noEmit",
"start": "node dist/index.js" "start": "node dist/index.js"
}, },
"devDependencies": { "devDependencies": {
"@tsconfig/node18": "^18.2.2",
"@tsconfig/strictest": "^2.0.2",
"@types/command-line-args": "^5.2.1", "@types/command-line-args": "^5.2.1",
"@types/node": "^18.18.4", "@types/node": "^18.18.4",
"@typescript-eslint/eslint-plugin": "^6.7.5", "@typescript-eslint/eslint-plugin": "^6.7.5",
@@ -20,11 +22,13 @@
"eslint": "^8.51.0", "eslint": "^8.51.0",
"eslint-config-google": "^0.14.0", "eslint-config-google": "^0.14.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "^2.28.1", "eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "npm:eslint-plugin-i@^2.28.1",
"eslint-plugin-matrix-org": "^1.2.1", "eslint-plugin-matrix-org": "^1.2.1",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-unicorn": "^48.0.1", "eslint-plugin-unicorn": "^48.0.1",
"prettier": "^3.0.3", "prettier": "^3.0.3",
"ts-node": "^10.9.1", "ts-node": "github:TypeStrong/ts-node#main",
"typescript": "^5.2.2" "typescript": "^5.2.2"
}, },
"dependencies": { "dependencies": {
@@ -35,6 +39,7 @@
"pg": "^8.11.3", "pg": "^8.11.3",
"sqlite3": "^5.1.6", "sqlite3": "^5.1.6",
"ts-command-line-args": "^2.5.1", "ts-command-line-args": "^2.5.1",
"yaml": "^2.3.2" "yaml": "^2.3.2",
"zod": "^3.22.4"
} }
} }

View File

@@ -1,15 +1,33 @@
import { readFile } from "node:fs/promises"; // Copyright 2023 The Matrix.org Foundation C.I.C.
import { parse } from "ts-command-line-args"; //
import log4js from "log4js"; // Licensed under the Apache License, Version 2.0 (the "License");
import yaml from "yaml"; // you may not use this file except in compliance with the License.
import { Knex } from "knex"; // You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { readFile } from "node:fs/promises";
import { Knex } from "knex";
import log4js from "log4js";
import { parse } from "ts-command-line-args";
import yaml from "yaml";
import { SUser } from "./types/SUser";
import { SynapseConfig, SynapseOIDCProvider } from "./types/SynapseConfig";
import { connectToSynapseDatabase } from "./db.mjs"; import { connectToSynapseDatabase } from "./db.mjs";
import { SUserThreePid } from "./types/SUserThreePid"; import {
import { SAccessToken } from "./types/SAccessToken"; synapseConfig as synapseConfigSchema,
import { SRefreshToken } from "./types/SRefreshToken"; SynapseOIDCProvider,
} from "./schemas/synapse.mjs";
import type { SAccessToken } from "./types/SAccessToken.d.ts";
import type { SRefreshToken } from "./types/SRefreshToken.d.ts";
import type { SUser } from "./types/SUser.d.ts";
import type { SUserThreePid } from "./types/SUserThreePid.d.ts";
const log = log4js.getLogger("migrate"); const log = log4js.getLogger("migrate");
@@ -19,15 +37,30 @@ interface Options {
help?: boolean; help?: boolean;
} }
export async function advisor(argv?: string[]): Promise<void> { export async function advisor(): Promise<void> {
const args = parse<Options>({ const args = parse<Options>(
command: { type: String, description: "Command to run", defaultOption: true, typeLabel: "migrate" }, {
synapseConfigFile: { type: String, description: "Path to synapse homeserver.yaml config file" }, command: {
help: { type: Boolean, optional: true, alias: "h", description: "Prints this usage guide" }, type: String,
description: "Command to run",
defaultOption: true,
typeLabel: "migrate",
},
synapseConfigFile: {
type: String,
description: "Path to synapse homeserver.yaml config file",
},
help: {
type: Boolean,
optional: true,
alias: "h",
description: "Prints this usage guide",
},
}, },
{ {
helpArg: "help", helpArg: "help",
}); },
);
const warnings: string[] = []; const warnings: string[] = [];
function warn(message: string): void { function warn(message: string): void {
@@ -42,111 +75,205 @@ export async function advisor(argv?: string[]): Promise<void> {
} }
// load synapse config // load synapse config
const synapseConfig: SynapseConfig = yaml.parse(await readFile(args.synapseConfigFile, "utf8")); const synapseConfig = synapseConfigSchema.parse(
yaml.parse(await readFile(args.synapseConfigFile, "utf8")),
);
// connect to synapse databases // connect to synapse databases
const synapse = connectToSynapseDatabase(synapseConfig); const synapse = connectToSynapseDatabase(synapseConfig);
async function count(query: Knex.QueryBuilder): Promise<number> { async function count(query: Knex.QueryBuilder): Promise<number> {
const res = await (query.first()); const res = await query.first();
if (!res) { if (!res) {
return 0; return 0;
} }
return res["count(*)"] as number; return res["count(*)"] as number;
} }
const adminUsers = await count(synapse.count("*").from<SUser>("users").where({ admin: 1 })); const adminUsers = await count(
synapse.count("*").from<SUser>("users").where({ admin: 1 }),
);
if (adminUsers > 0) { if (adminUsers > 0) {
warn(`Synapse database contains ${adminUsers} admin users which will need to be added to the MAS configuration.`); warn(
`Synapse database contains ${adminUsers} admin users which will need to be added to the MAS configuration.`,
);
} }
const guestUsers = await count(synapse.count("*").from<SUser>("users").where({ is_guest: 1 })); const guestUsers = await count(
synapse.count("*").from<SUser>("users").where({ is_guest: 1 }),
);
if (guestUsers > 0) { if (guestUsers > 0) {
error(`Synapse database contains ${guestUsers} guest users which aren't supported by MAS: https://github.com/matrix-org/matrix-authentication-service/issues/1445`); error(
`Synapse database contains ${guestUsers} guest users which aren't supported by MAS: https://github.com/matrix-org/matrix-authentication-service/issues/1445`,
);
} }
if (synapseConfig.allow_guest_access) { if (synapseConfig.allow_guest_access) {
if (guestUsers > 0) { if (guestUsers > 0) {
error("Synapse config allows guest access which isn't supported by MAS: https://github.com/matrix-org/matrix-authentication-service/issues/1445"); error(
"Synapse config allows guest access which isn't supported by MAS: https://github.com/matrix-org/matrix-authentication-service/issues/1445",
);
} else { } else {
error("Synapse config allows guest access which isn't supported by MAS, but no guest users were found in the database so the option could be disabled: https://github.com/matrix-org/matrix-authentication-service/issues/1445"); error(
"Synapse config allows guest access which isn't supported by MAS, but no guest users were found in the database so the option could be disabled: https://github.com/matrix-org/matrix-authentication-service/issues/1445",
);
} }
} }
if (synapseConfig.enable_registration) { if (synapseConfig.enable_registration) {
warn("Synapse config has registration enabled which will need to be disabled after migration"); warn(
"Synapse config has registration enabled which will need to be disabled after migration",
);
} }
if (synapseConfig.enable_registration_captcha) { if (synapseConfig.enable_registration_captcha) {
error("Synapse config has registration CAPTCHA enabled which isn't supported by MAS: https://github.com/matrix-org/matrix-authentication-service/issues/138"); error(
"Synapse config has registration CAPTCHA enabled which isn't supported by MAS: https://github.com/matrix-org/matrix-authentication-service/issues/138",
);
} }
if (synapseConfig.user_consent) { if (synapseConfig.user_consent) {
warn("Synapse config has user_consent configured which will need to be disabled after migration"); warn(
"Synapse config has user_consent configured which will need to be disabled after migration",
);
} }
const usersWithoutEmailAddress = await count(synapse.count("*").from<SUser>("users").leftOuterJoin<SUserThreePid>("user_threepids", "users.name", "user_threepids.user_id").whereNull("user_threepids.user_id")); const usersWithoutEmailAddress = await count(
synapse
.count("*")
.from<SUser>("users")
.leftOuterJoin<SUserThreePid>(
"user_threepids",
"users.name",
"user_threepids.user_id",
)
.whereNull("user_threepids.user_id"),
);
if (usersWithoutEmailAddress > 0) { if (usersWithoutEmailAddress > 0) {
warn(`Synapse database contains ${usersWithoutEmailAddress} users without a verified email address who will need to verify their email address before they can login after migration: https://github.com/matrix-org/matrix-authentication-service/issues/1505`); warn(
`Synapse database contains ${usersWithoutEmailAddress} users without a verified email address who will need to verify their email address before they can login after migration: https://github.com/matrix-org/matrix-authentication-service/issues/1505`,
);
} }
const deactivatedUsers = await count(synapse.count("*").from<SUser>("users").where({ deactivated: 1 })); const deactivatedUsers = await count(
synapse.count("*").from<SUser>("users").where({ deactivated: 1 }),
);
if (deactivatedUsers > 0) { if (deactivatedUsers > 0) {
error(`Synapse database contains ${deactivatedUsers} deactivated users which aren't supported during migration`); error(
`Synapse database contains ${deactivatedUsers} deactivated users which aren't supported during migration`,
);
} }
const accessTokensWithoutDeviceId = await count(synapse.count("*").from<SAccessToken>("access_tokens").where({ device_id: "" }).orWhereNull("device_id")); const accessTokensWithoutDeviceId = await count(
synapse
.count("*")
.from<SAccessToken>("access_tokens")
.where({ device_id: "" })
.orWhereNull("device_id"),
);
if (accessTokensWithoutDeviceId > 0) { if (accessTokensWithoutDeviceId > 0) {
error(`Synapse database contains ${accessTokensWithoutDeviceId} access tokens without an associated device_id which aren't supported during migration`); error(
`Synapse database contains ${accessTokensWithoutDeviceId} access tokens without an associated device_id which aren't supported during migration`,
);
} }
const nonEmailThreePids = await count(synapse.count("*").from<SUserThreePid>("user_threepids").whereNot({ medium: "email" })); const nonEmailThreePids = await count(
synapse
.count("*")
.from<SUserThreePid>("user_threepids")
.whereNot({ medium: "email" }),
);
if (nonEmailThreePids > 0) { if (nonEmailThreePids > 0) {
error(`Synapse database contains ${nonEmailThreePids} non-email 3pids which will be ignored during migration`); error(
`Synapse database contains ${nonEmailThreePids} non-email 3pids which will be ignored during migration`,
);
} }
const oidcProviders: SynapseOIDCProvider[] = [...(synapseConfig.oidc_providers ?? []), ...(synapseConfig.oidc_config ? [synapseConfig.oidc_config] : [])]; const oidcProviders: SynapseOIDCProvider[] = [
...(synapseConfig.oidc_providers ?? []),
...(synapseConfig.oidc_config ? [synapseConfig.oidc_config] : []),
];
for (const provider of oidcProviders) { for (const provider of oidcProviders) {
warn(`Synapse config contains OIDC auth configuration which will need mapping to be manually mapped to an upstream OpenID Provider during migration: ${provider.issuer}`); warn(
`Synapse config contains OIDC auth configuration which will need mapping to be manually mapped to an upstream OpenID Provider during migration: ${provider.issuer}`,
);
} }
if (synapseConfig.cas_config?.enabled) { if (synapseConfig.cas_config?.enabled) {
warn("Synapse config contains CAS auth configuration which will need mapping to be manually mapped to an upstream OpenID Provider during migration"); warn(
"Synapse config contains CAS auth configuration which will need mapping to be manually mapped to an upstream OpenID Provider during migration",
);
} }
if (synapseConfig.saml2_config?.sp_config) { if (synapseConfig.saml2_config?.sp_config) {
warn("Synapse config contains SAML2 auth configuration which will need mapping to be manually mapped to an upstream OpenID Provider during migration"); warn(
"Synapse config contains SAML2 auth configuration which will need mapping to be manually mapped to an upstream OpenID Provider during migration",
);
} }
if (synapseConfig.jwt_config?.enabled) { if (synapseConfig.jwt_config?.enabled) {
warn("Synapse config contains JWT auth configuration which will need mapping to be manually mapped to an upstream OpenID Provider during migration"); warn(
"Synapse config contains JWT auth configuration which will need mapping to be manually mapped to an upstream OpenID Provider during migration",
);
} }
if (synapseConfig.password_config?.enabled !== false && synapseConfig.password_config?.localdb_enabled === false) { if (
warn("Synapse has a non-standard password auth enabled which won't work after migration and will need to be manually mapped to an upstream OpenID Provider during migration"); synapseConfig.password_config?.enabled !== false &&
synapseConfig.password_config?.localdb_enabled === false
) {
warn(
"Synapse has a non-standard password auth enabled which won't work after migration and will need to be manually mapped to an upstream OpenID Provider during migration",
);
} else if (synapseConfig.password_config?.enabled !== false) { } else if (synapseConfig.password_config?.enabled !== false) {
warn("Synapse has password auth enabled, but support for password auth in MAS is not feature complete"); warn(
"Synapse has password auth enabled, but support for password auth in MAS is not feature complete",
);
} }
const externalIdAuthProviders = await synapse.select("auth_provider").count("* as Count").from("user_external_ids").groupBy("auth_provider") as { auth_provider: string; "Count": number }[]; const externalIdAuthProviders = (await synapse
.select("auth_provider")
.count("* as Count")
.from("user_external_ids")
.groupBy("auth_provider")) as { auth_provider: string; Count: number }[];
for (const row of externalIdAuthProviders) { for (const row of externalIdAuthProviders) {
warn(`An upstream OpenID Provider will need to be configured for the ${row.Count} users with auth provider ${row.auth_provider}`); warn(
`An upstream OpenID Provider will need to be configured for the ${row.Count} users with auth provider ${row.auth_provider}`,
);
} }
const usersWithPassword = await count(synapse.count("*").from<SUser>("users").whereNotNull("password_hash")); const usersWithPassword = await count(
synapse.count("*").from<SUser>("users").whereNotNull("password_hash"),
);
if (usersWithPassword > 0) { if (usersWithPassword > 0) {
warn(`Synapse database contains ${usersWithPassword} users with a password which will be migrated. However, support for password auth in MAS is not feature complete`); warn(
`Synapse database contains ${usersWithPassword} users with a password which will be migrated. However, support for password auth in MAS is not feature complete`,
);
} }
const accessTokensToImport = await count(synapse.count("*").from<SAccessToken>("access_tokens").whereNotNull("device_id")); const accessTokensToImport = await count(
synapse
.count("*")
.from<SAccessToken>("access_tokens")
.whereNotNull("device_id"),
);
if (accessTokensToImport > 0) { if (accessTokensToImport > 0) {
log.info(`Synapse database contains ${accessTokensToImport} access tokens which will be migrated`); log.info(
`Synapse database contains ${accessTokensToImport} access tokens which will be migrated`,
);
} }
const synapseRefreshToken = await count(synapse.select("*").from<SRefreshToken>("refresh_tokens")); const synapseRefreshToken = await count(
synapse.select("*").from<SRefreshToken>("refresh_tokens"),
);
if (synapseRefreshToken > 0) { if (synapseRefreshToken > 0) {
log.info(`Synapse database contains ${synapseRefreshToken} refresh tokens which will be migrated`); log.info(
`Synapse database contains ${synapseRefreshToken} refresh tokens which will be migrated`,
);
} }
if (synapseConfig.enable_3pid_changes === true) { if (synapseConfig.enable_3pid_changes === true) {
warn("Synapse config has enable_3pid_changes enabled which must to be disabled or removed after migration"); warn(
"Synapse config has enable_3pid_changes enabled which must to be disabled or removed after migration",
);
} }
if (synapseConfig.login_via_existing_session?.enabled === true) { if (synapseConfig.login_via_existing_session?.enabled === true) {
warn("Synapse config has login_via_existing_session enabled which must to be disabled or removed after migration"); warn(
"Synapse config has login_via_existing_session enabled which must to be disabled or removed after migration",
);
} }
} }

View File

@@ -1,43 +1,76 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import knex, { Knex } from "knex"; import knex, { Knex } from "knex";
import { DatabaseConfig, Psycopg2DatabaseConfig, Sqlite3DatabaseConfig, SynapseConfig } from "./types/SynapseConfig"; import {
import { MASConfig } from "./types/MASConfig"; MASConfig,
DatabaseConfig as MASDatabaseConfig,
URIDatabaseConfig as MASURIDatabaseConfig,
} from "./schemas/mas.mjs";
import { SynapseConfig } from "./schemas/synapse.mjs";
function isSqlite3(config: DatabaseConfig): config is Sqlite3DatabaseConfig { export function connectToSynapseDatabase({
return config.name === "sqlite3"; database,
} }: SynapseConfig): Knex<{}, unknown[]> {
function isPsycopg2(config: DatabaseConfig): config is Psycopg2DatabaseConfig {
return config.name === "psycopg2";
}
export function connectToSynapseDatabase({ database }: SynapseConfig): Knex<{}, unknown[]> {
if (!database) { if (!database) {
throw new Error("Synapse database not configured"); throw new Error("Synapse database not configured");
} }
if (isSqlite3(database)) { if (database.name === "sqlite3") {
const filename = database.args?.database; return knex({
if (!filename) { client: "sqlite3",
throw new Error("Synapse sqlite3 database not configured"); connection: { filename: database.args.database },
useNullAsDefault: true,
});
} }
return knex({ client: "sqlite3", connection: { filename }, useNullAsDefault: true }); const connection: Knex.PgConnectionConfig = {};
} database.args.database && (connection.database = database.args.database);
database.args.user && (connection.user = database.args.user);
database.args.password && (connection.password = database.args.password);
typeof database.args.port === "number" &&
(connection.port = database.args.port);
typeof database.args.port === "string" &&
(connection.port = parseInt(database.args.port));
if (isPsycopg2(database)) { return knex({
return knex({ client: "pg", connection: { client: "pg",
user: database?.args?.user, connection,
database: database?.args?.database, });
password: database?.args?.password,
port: database?.args?.port,
host: database?.args?.host,
} });
}
throw new Error(`Unsupported database type ${database?.name}. Must be sqlite3 or psycopg2`);
} }
export function connectToMASDatabase({ database }: MASConfig): Knex<{}, unknown[]> { const isUriConfig = (
return knex({ client: "pg", connection: database?.uri }); database: MASDatabaseConfig,
): database is MASURIDatabaseConfig =>
typeof (database as Record<string, unknown>)["uri"] === "string";
export function connectToMASDatabase({
database,
}: MASConfig): Knex<{}, unknown[]> {
const connection: Knex.PgConnectionConfig = {};
if (isUriConfig(database)) {
connection.connectionString = database.uri;
} else {
database.database && (connection.database = database.database);
database.username && (connection.user = database.username);
database.password && (connection.password = database.password);
database.port && (connection.port = database.port);
}
return knex({
client: "pg",
connection,
});
} }

View File

@@ -1,8 +1,22 @@
import { ArgumentConfig, parse } from "ts-command-line-args"; // Copyright 2023 The Matrix.org Foundation C.I.C.
import log4js from "log4js"; //
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import log4js from "log4js";
import { ArgumentConfig, parse } from "ts-command-line-args";
import { migrate } from "./migrate.mjs";
import { advisor } from "./advisor.mjs"; import { advisor } from "./advisor.mjs";
import { migrate } from "./migrate.mjs";
log4js.configure({ log4js.configure({
appenders: { appenders: {
@@ -21,11 +35,23 @@ interface MainOptions {
} }
const mainArgOptions: ArgumentConfig<MainOptions> = { const mainArgOptions: ArgumentConfig<MainOptions> = {
command: { type: String, description: "Command to run", defaultOption: true, typeLabel: "<advisor|migrate>" }, command: {
help: { type: Boolean, optional: true, alias: "h", description: "Prints this usage guide" }, type: String,
description: "Command to run",
defaultOption: true,
typeLabel: "<advisor|migrate>",
},
help: {
type: Boolean,
optional: true,
alias: "h",
description: "Prints this usage guide",
},
}; };
export const mainArgs = parse<MainOptions>(mainArgOptions, { stopAtFirstUnknown: true }); export const mainArgs = parse<MainOptions>(mainArgOptions, {
stopAtFirstUnknown: true,
});
try { try {
if (mainArgs.command === "migrate") { if (mainArgs.command === "migrate") {

View File

@@ -1,25 +1,40 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { readFile } from "node:fs/promises"; import { readFile } from "node:fs/promises";
import { parse } from "ts-command-line-args";
import id128 from "id128"; import id128 from "id128";
import log4js from "log4js"; import log4js from "log4js";
import { parse } from "ts-command-line-args";
import yaml from "yaml"; import yaml from "yaml";
import { SUser } from "./types/SUser";
import { SUserThreePid } from "./types/SUserThreePid";
import { MUserPassword } from "./types/MUserPassword";
import { MUserEmail } from "./types/MUserEmail";
import { SUserExternalId } from "./types/SUserExternalId";
import { SAccessToken } from "./types/SAccessToken";
import { SRefreshToken } from "./types/SRefreshToken";
import { MCompatAccessToken } from "./types/MCompatAccessToken";
import { MCompatRefreshToken } from "./types/MCompatRefreshToken";
import { MCompatSession } from "./types/MCompatSession";
import { MUpstreamOauthLink } from "./types/MUpstreamOauthLink";
import { MUpstreamOauthProvider } from "./types/MUpstreamOauthProvider";
import { UUID } from "./types";
import { SynapseConfig } from "./types/SynapseConfig";
import { MASConfig } from "./types/MASConfig";
import { connectToSynapseDatabase, connectToMASDatabase } from "./db.mjs"; import { connectToSynapseDatabase, connectToMASDatabase } from "./db.mjs";
import { masConfig as masConfigSchema } from "./schemas/mas.mjs";
import { synapseConfig as synapseConfigSchema } from "./schemas/synapse.mjs";
import type { MCompatAccessToken } from "./types/MCompatAccessToken.d.ts";
import type { MCompatRefreshToken } from "./types/MCompatRefreshToken.d.ts";
import type { MCompatSession } from "./types/MCompatSession.d.ts";
import type { MUpstreamOauthLink } from "./types/MUpstreamOauthLink.d.ts";
import type { MUpstreamOauthProvider } from "./types/MUpstreamOauthProvider.d.ts";
import type { MUserEmail } from "./types/MUserEmail.d.ts";
import type { MUserPassword } from "./types/MUserPassword.d.ts";
import type { SAccessToken } from "./types/SAccessToken.d.ts";
import type { SRefreshToken } from "./types/SRefreshToken.d.ts";
import type { SUser } from "./types/SUser.d.ts";
import type { SUserExternalId } from "./types/SUserExternalId.d.ts";
import type { SUserThreePid } from "./types/SUserThreePid.d.ts";
import type { UUID } from "./types/index.d.ts";
const log = log4js.getLogger("migrate"); const log = log4js.getLogger("migrate");
@@ -32,19 +47,44 @@ interface MigrationOptions {
help?: boolean; help?: boolean;
} }
export async function migrate(argv?: string[]): Promise<void> { export async function migrate(): Promise<void> {
const args = parse<MigrationOptions>({ const args = parse<MigrationOptions>(
command: { type: String, description: "Command to run", defaultOption: true, typeLabel: "migrate" }, {
synapseConfigFile: { type: String, description: "Path to synapse homeserver.yaml config file" }, command: {
type: String,
description: "Command to run",
defaultOption: true,
typeLabel: "migrate",
},
synapseConfigFile: {
type: String,
description: "Path to synapse homeserver.yaml config file",
},
masConfigFile: { type: String, description: "Path to MAS config.yaml" }, masConfigFile: { type: String, description: "Path to MAS config.yaml" },
upstreamProviderMapping: { type: String, defaultValue: [], multiple: true, description: "Mapping of upstream provider IDs to MAS provider IDs. Format: <upstream_provider_id>:<mas_provider_id>" }, upstreamProviderMapping: {
dryRun: { type: Boolean, optional: true, defaultValue: false, description: "Dry run only, do not write to database" }, type: String,
help: { type: Boolean, optional: true, alias: "h", description: "Prints this usage guide" }, defaultValue: [],
multiple: true,
description:
"Mapping of upstream provider IDs to MAS provider IDs. Format: <upstream_provider_id>:<mas_provider_id>",
},
dryRun: {
type: Boolean,
optional: true,
defaultValue: false,
description: "Dry run only, do not write to database",
},
help: {
type: Boolean,
optional: true,
alias: "h",
description: "Prints this usage guide",
},
}, },
{ {
helpArg: "help", helpArg: "help",
argv, },
}); );
const warnings: string[] = []; const warnings: string[] = [];
function warn(message: string): void { function warn(message: string): void {
@@ -62,35 +102,56 @@ export async function migrate(argv?: string[]): Promise<void> {
} }
function makeUuid<T>(): UUID<T> { function makeUuid<T>(): UUID<T> {
return id128.Uuid4.fromRaw(id128.UlidMonotonic.generate().toRaw()).toCanonical(); return id128.Uuid4.fromRaw(
id128.UlidMonotonic.generate().toRaw(),
).toCanonical();
} }
// load synapse config // load synapse config
const synapseConfig: SynapseConfig = yaml.parse(await readFile(args.synapseConfigFile, "utf8")); const synapseConfig = synapseConfigSchema.parse(
yaml.parse(await readFile(args.synapseConfigFile, "utf8")),
);
// connect to synapse databases // connect to synapse database
const synapse = connectToSynapseDatabase(synapseConfig); const synapse = connectToSynapseDatabase(synapseConfig);
// load MAS config // load MAS config
const masConfig: MASConfig = yaml.parse(await readFile(args.masConfigFile, "utf8")); const masConfig = masConfigSchema.parse(
yaml.parse(await readFile(args.masConfigFile, "utf8")),
);
if (!masConfig.database?.uri) { // connect to MAS database
log.fatal("Missing database URI in MAS config");
process.exit(1);
}
const mas = connectToMASDatabase(masConfig); const mas = connectToMASDatabase(masConfig);
const upstreamProviders = new Map<string, MUpstreamOauthProvider>(); const upstreamProviders = new Map<string, MUpstreamOauthProvider>();
for (const mapping of args.upstreamProviderMapping) { for (const mapping of args.upstreamProviderMapping) {
const [providerId, masProviderId] = mapping.split(":"); const [providerId, masProviderId] = mapping.split(":");
if (!id128.Uuid.isRaw(masProviderId) && !id128.Uuid.isCanonical(masProviderId)) { if (!providerId || !masProviderId) {
throw new Error(`Upstream provider mapping UUID is not in correct format. It should be a UUID: ${masProviderId}`); throw new Error(
`Upstream provider mapping is not in correct format. It should be <upstream_provider_id>:<mas_provider_id>: ${mapping}`,
);
} }
log.info(`Loading existing upstream provider ${masProviderId} from MAS database as ${providerId}`);
const existingProvider = await mas("upstream_oauth_providers").select("*").where({ upstream_oauth_provider_id: masProviderId }).first(); if (
!id128.Uuid.isRaw(masProviderId) &&
!id128.Uuid.isCanonical(masProviderId)
) {
throw new Error(
`Upstream provider mapping UUID is not in correct format. It should be a UUID: ${masProviderId}`,
);
}
log.info(
`Loading existing upstream provider ${masProviderId} from MAS database as ${providerId}`,
);
const existingProvider = await mas("upstream_oauth_providers")
.select("*")
.where({ upstream_oauth_provider_id: masProviderId })
.first();
if (!existingProvider) { if (!existingProvider) {
throw new Error(`Could not find upstream provider ${masProviderId} in MAS database`); throw new Error(
`Could not find upstream provider ${masProviderId} in MAS database`,
);
} }
upstreamProviders.set(providerId, existingProvider); upstreamProviders.set(providerId, existingProvider);
} }
@@ -98,15 +159,23 @@ export async function migrate(argv?: string[]): Promise<void> {
function stringifyAndRedact(input: unknown): string { function stringifyAndRedact(input: unknown): string {
const x = JSON.stringify(input); const x = JSON.stringify(input);
return x.replace(/("(password_hash|hashed_password|access_token|token)":")[^"]*"/, "$1redacted\""); return x.replace(
/("(password_hash|hashed_password|access_token|token)":")[^"]*"/,
'$1redacted"',
);
} }
type Execution = (() => Promise<void>); type Execution = () => Promise<void>;
const existingMasUsers = await mas.count({ count: "*" }).from("users").first(); const existingMasUsers = await mas
.count({ count: "*" })
.from("users")
.first();
if (parseInt(`${existingMasUsers?.count ?? 0}`) > 0) { if (parseInt(`${existingMasUsers?.count ?? 0}`) > 0) {
fatal(`Found ${existingMasUsers?.count} existing users in MAS. Refusing to continue. Please clean MAS and try again.`); fatal(
`Found ${existingMasUsers?.count} existing users in MAS. Refusing to continue. Please clean MAS and try again.`,
);
} }
const synapseUsers = await synapse.select("*").from<SUser>("users"); const synapseUsers = await synapse.select("*").from<SUser>("users");
@@ -144,17 +213,26 @@ export async function migrate(argv?: string[]): Promise<void> {
version: 1, version: 1,
}; };
log.debug(`Password ${user.password_hash.slice(-4)} => ${stringifyAndRedact(masUserPassword)}`); log.debug(
`Password ${user.password_hash.slice(-4)} => ${stringifyAndRedact(
masUserPassword,
)}`,
);
executions.push(() => mas.insert(masUserPassword).into("user_passwords")); executions.push(() => mas.insert(masUserPassword).into("user_passwords"));
} }
// user_threepids => user_emails // user_threepids => user_emails
let primaryEmail: MUserEmail | undefined; let primaryEmail: MUserEmail | undefined;
const synapseThreePids = await synapse.select("*").from<SUserThreePid>("user_threepids").where({ user_id: user.name }); const synapseThreePids = await synapse
.select("*")
.from<SUserThreePid>("user_threepids")
.where({ user_id: user.name });
for (const threePid of synapseThreePids) { for (const threePid of synapseThreePids) {
if (threePid.medium !== "email") { if (threePid.medium !== "email") {
warningsForUser += 1; warningsForUser += 1;
warn(`Skipping non-email 3pid ${threePid.medium} for user ${user.name}`); warn(
`Skipping non-email 3pid ${threePid.medium} for user ${user.name}`,
);
continue; continue;
} }
const masUserEmail: MUserEmail = { const masUserEmail: MUserEmail = {
@@ -162,26 +240,46 @@ export async function migrate(argv?: string[]): Promise<void> {
user_id: masUser.user_id, user_id: masUser.user_id,
email: threePid.address.toLowerCase(), email: threePid.address.toLowerCase(),
created_at: new Date(parseInt(`${threePid.added_at}`) * 1000), created_at: new Date(parseInt(`${threePid.added_at}`) * 1000),
confirmed_at: threePid.validated_at ? new Date(parseInt(`${threePid.validated_at}`) * 1000) : undefined,
}; };
log.debug(`${stringifyAndRedact(threePid)} => ${stringifyAndRedact(masUserEmail)}`); if (threePid.validated_at) {
masUserEmail.confirmed_at = new Date(
parseInt(`${threePid.validated_at}`) * 1000,
);
}
log.debug(
`${stringifyAndRedact(threePid)} => ${stringifyAndRedact(
masUserEmail,
)}`,
);
if (!primaryEmail && threePid.validated_at) { if (!primaryEmail && threePid.validated_at) {
primaryEmail = masUserEmail; primaryEmail = masUserEmail;
} }
executions.push(() => mas.insert(masUserEmail).into("user_emails")); executions.push(() => mas.insert(masUserEmail).into("user_emails"));
} }
if (primaryEmail) { if (primaryEmail) {
log.debug(`Setting primary email for existing user ${masUser.username} to ${primaryEmail.email} as update`); log.debug(
executions.push(() => mas("users").where({ user_id: masUser!.user_id }).update({ primary_user_email_id: primaryEmail!.user_email_id })); `Setting primary email for existing user ${masUser.username} to ${primaryEmail.email} as update`,
);
executions.push(() =>
mas("users")
.where({ user_id: masUser!.user_id })
.update({ primary_user_email_id: primaryEmail!.user_email_id }),
);
} }
// user_external_ids => upstream_oauth_links // user_external_ids => upstream_oauth_links
const synapseExternalIds = await synapse.select("*").from<SUserExternalId>("user_external_ids").where({ user_id: user.name }); const synapseExternalIds = await synapse
.select("*")
.from<SUserExternalId>("user_external_ids")
.where({ user_id: user.name });
for (const externalId of synapseExternalIds) { for (const externalId of synapseExternalIds) {
try { try {
if (!upstreamProviders.has(externalId.auth_provider)) { if (!upstreamProviders.has(externalId.auth_provider)) {
throw new Error(`Unknown upstream provider ${externalId.auth_provider}`); throw new Error(
`Unknown upstream provider ${externalId.auth_provider}`,
);
} }
const provider = upstreamProviders.get(externalId.auth_provider)!; const provider = upstreamProviders.get(externalId.auth_provider)!;
const masUpstreamOauthLink: MUpstreamOauthLink = { const masUpstreamOauthLink: MUpstreamOauthLink = {
@@ -192,20 +290,33 @@ export async function migrate(argv?: string[]): Promise<void> {
created_at: masUser.created_at, created_at: masUser.created_at,
}; };
log.debug(`${stringifyAndRedact(synapseExternalIds)} => ${stringifyAndRedact(masUpstreamOauthLink)}`); log.debug(
`${stringifyAndRedact(synapseExternalIds)} => ${stringifyAndRedact(
masUpstreamOauthLink,
)}`,
);
executions.push(() => mas.insert(masUpstreamOauthLink).into("upstream_oauth_links")); executions.push(() =>
mas.insert(masUpstreamOauthLink).into("upstream_oauth_links"),
);
} catch (e) { } catch (e) {
fatal(`Failed to import external id ${externalId.external_id} with ${externalId.auth_provider} for user ${user.name}: ${e}`); fatal(
`Failed to import external id ${externalId.external_id} with ${externalId.auth_provider} for user ${user.name}: ${e}`,
);
} }
} }
// access_tokens,refresh_tokens => compat_sessions,compat_access_tokens // access_tokens,refresh_tokens => compat_sessions,compat_access_tokens
const synapseAccessTokens = await synapse.select("*").from<SAccessToken>("access_tokens").where({ user_id: user.name }); const synapseAccessTokens = await synapse
.select("*")
.from<SAccessToken>("access_tokens")
.where({ user_id: user.name });
for (const accessToken of synapseAccessTokens) { for (const accessToken of synapseAccessTokens) {
if (!accessToken.device_id) { if (!accessToken.device_id) {
warningsForUser += 1; warningsForUser += 1;
warn(`Skipping access token ${accessToken.token} for user ${user.name} with no device_id`); warn(
`Skipping access token ${accessToken.token} for user ${user.name} with no device_id`,
);
continue; continue;
} }
@@ -213,11 +324,19 @@ export async function migrate(argv?: string[]): Promise<void> {
compat_session_id: makeUuid(), compat_session_id: makeUuid(),
user_id: masUser.user_id, user_id: masUser.user_id,
device_id: accessToken.device_id, device_id: accessToken.device_id,
created_at: accessToken.last_validated ? new Date(parseInt(`${accessToken.last_validated}`)) : masUser.created_at, created_at: accessToken.last_validated
? new Date(parseInt(`${accessToken.last_validated}`))
: masUser.created_at,
is_synapse_admin: user.admin === 1, is_synapse_admin: user.admin === 1,
}; };
log.debug(`${stringifyAndRedact(accessToken)} => ${stringifyAndRedact(masCompatSession)}`); log.debug(
executions.push(() => mas.insert(masCompatSession).into("compat_sessions")); `${stringifyAndRedact(accessToken)} => ${stringifyAndRedact(
masCompatSession,
)}`,
);
executions.push(() =>
mas.insert(masCompatSession).into("compat_sessions"),
);
const masCompatAccessToken: MCompatAccessToken = { const masCompatAccessToken: MCompatAccessToken = {
compat_access_token_id: makeUuid(), compat_access_token_id: makeUuid(),
@@ -225,11 +344,21 @@ export async function migrate(argv?: string[]): Promise<void> {
access_token: accessToken.token, access_token: accessToken.token,
created_at: masCompatSession.created_at, created_at: masCompatSession.created_at,
}; };
log.debug(`Access token ${accessToken.id} => ${stringifyAndRedact(masCompatAccessToken)}`); log.debug(
executions.push(() => mas.insert(masCompatAccessToken).into("compat_access_tokens")); `Access token ${accessToken.id} => ${stringifyAndRedact(
masCompatAccessToken,
)}`,
);
executions.push(() =>
mas.insert(masCompatAccessToken).into("compat_access_tokens"),
);
if (accessToken.refresh_token_id) { if (accessToken.refresh_token_id) {
const synapseRefreshToken = await synapse.select("*").from<SRefreshToken>("refresh_tokens").where({ id: accessToken.refresh_token_id }).first(); const synapseRefreshToken = await synapse
.select("*")
.from<SRefreshToken>("refresh_tokens")
.where({ id: accessToken.refresh_token_id })
.first();
if (synapseRefreshToken) { if (synapseRefreshToken) {
const masCompatRefreshToken: MCompatRefreshToken = { const masCompatRefreshToken: MCompatRefreshToken = {
compat_refresh_token_id: makeUuid(), compat_refresh_token_id: makeUuid(),
@@ -238,11 +367,19 @@ export async function migrate(argv?: string[]): Promise<void> {
refresh_token: synapseRefreshToken.token, refresh_token: synapseRefreshToken.token,
created_at: masCompatSession.created_at, created_at: masCompatSession.created_at,
}; };
log.debug(`Refresh token ${synapseRefreshToken.id} => ${stringifyAndRedact(masCompatRefreshToken)}`); log.debug(
executions.push(() => mas.insert(masCompatRefreshToken).into("compat_refresh_tokens")); `Refresh token ${synapseRefreshToken.id} => ${stringifyAndRedact(
masCompatRefreshToken,
)}`,
);
executions.push(() =>
mas.insert(masCompatRefreshToken).into("compat_refresh_tokens"),
);
} else { } else {
warningsForUser += 1; warningsForUser += 1;
warn(`Unable to locate refresh token ${accessToken.refresh_token_id} for user ${user.name}`); warn(
`Unable to locate refresh token ${accessToken.refresh_token_id} for user ${user.name}`,
);
} }
} }
} }
@@ -272,7 +409,11 @@ export async function migrate(argv?: string[]): Promise<void> {
} }
} }
} }
log.info(`Completed migration ${args.dryRun ? "dry-run " : ""}of ${synapseUsers.length} users with ${fatals} fatals and ${warnings.length} warnings:`); log.info(
`Completed migration ${args.dryRun ? "dry-run " : ""}of ${
synapseUsers.length
} users with ${fatals} fatals and ${warnings.length} warnings:`,
);
warnings.forEach((w) => log.warn(w)); warnings.forEach((w) => log.warn(w));
if (fatals > 0) { if (fatals > 0) {
throw new Error(`Migration failed with ${fatals} fatals`); throw new Error(`Migration failed with ${fatals} fatals`);

View File

@@ -0,0 +1,44 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { z } from "zod";
const uriDatabaseConfig = z.object({
uri: z.string(),
});
export type URIDatabaseConfig = z.infer<typeof uriDatabaseConfig>;
const objectDatabaseConfig = z.object({
host: z.string().optional(),
port: z.number().optional(),
username: z.string().optional(),
password: z.string().optional(),
database: z.string().optional(),
});
const databaseConfig = z.union([uriDatabaseConfig, objectDatabaseConfig]);
export type DatabaseConfig = z.infer<typeof databaseConfig>;
const secretsConfig = z.object({
encryption: z.string(),
});
export const masConfig = z.object({
database: databaseConfig,
secrets: secretsConfig,
});
export type MASConfig = z.infer<typeof masConfig>;

View File

@@ -0,0 +1,99 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { z } from "zod";
const sqlite3DatabaseConfig = z.object({
name: z.literal("sqlite3"),
args: z.object({
database: z.string(),
}),
});
const psycopg2DatabaseConfig = z.object({
name: z.literal("psycopg2"),
args: z.object({
user: z.string().nullish(),
password: z.string().nullish(),
database: z.string().nullish(),
host: z.string().nullish(),
port: z.union([z.number(), z.string()]).nullish(),
}),
});
const databaseConfig = z.union([sqlite3DatabaseConfig, psycopg2DatabaseConfig]);
const oidcProviderConfig = z.object({
idp_id: z.string(),
idp_name: z.string(),
issuer: z.string(),
client_id: z.string(),
scopes: z.array(z.string()),
client_auth_method: z
.union([
z.literal("client_secret_basic"),
z.literal("client_secret_post"),
z.literal("none"),
])
.nullish(),
client_secret: z.string().nullish(),
client_secret_jwt_key: z.string().nullish(),
});
export type SynapseOIDCProvider = z.infer<typeof oidcProviderConfig>;
export const synapseConfig = z.object({
database: databaseConfig,
oidc_providers: z.array(oidcProviderConfig).nullish(),
oidc_config: oidcProviderConfig.nullish(),
allow_guest_access: z.boolean().nullish(),
cas_config: z
.object({
enabled: z.boolean().nullish(),
})
.nullish(),
saml2_config: z
.object({
sp_config: z.object({}).nullish(),
})
.nullish(),
sso: z
.object({
client_whitelist: z.array(z.string()).nullish(),
update_profile_information: z.boolean().nullish(),
})
.nullish(),
jwt_config: z
.object({
enabled: z.boolean().nullish(),
})
.nullish(),
password_config: z
.object({
enabled: z.boolean().nullish(),
localdb_enabled: z.boolean().nullish(),
})
.nullish(),
enable_registration_captcha: z.boolean().nullish(),
enable_registration: z.boolean().nullish(),
user_consent: z.object({}).nullish(),
enable_3pid_changes: z.boolean().nullish(),
login_via_existing_session: z
.object({
enabled: z.boolean().nullish(),
})
.nullish(),
});
export type SynapseConfig = z.infer<typeof synapseConfig>;

View File

@@ -1,8 +0,0 @@
export interface MASConfig {
database?: {
uri?: string;
};
secrets?: {
encryption?: string;
};
}

View File

@@ -1,6 +1,21 @@
import { UUID } from "./index"; // Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { MCompatSession } from "./MCompatSession"; import { MCompatSession } from "./MCompatSession";
import { UUID } from "./index";
/* /*
+------------------------+--------------------------+-----------+ +------------------------+--------------------------+-----------+
| Column | Type | Modifiers | | Column | Type | Modifiers |

View File

@@ -1,7 +1,22 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { MCompatAccessToken } from "./MCompatAccessToken"; import { MCompatAccessToken } from "./MCompatAccessToken";
import { UUID } from "./index";
import { MCompatSession } from "./MCompatSession"; import { MCompatSession } from "./MCompatSession";
import { UUID } from "./index";
/* /*
+-------------------------+--------------------------+-----------+ +-------------------------+--------------------------+-----------+
| Column | Type | Modifiers | | Column | Type | Modifiers |

View File

@@ -1,4 +1,19 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { MUser } from "./MUser"; import { MUser } from "./MUser";
import { UUID } from "./index"; import { UUID } from "./index";
/* /*

View File

@@ -1,6 +1,21 @@
import { MUser } from "./MUser"; // Copyright 2023 The Matrix.org Foundation C.I.C.
import { UUID } from "./index"; //
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { MUpstreamOauthProvider } from "./MUpstreamOauthProvider"; import { MUpstreamOauthProvider } from "./MUpstreamOauthProvider";
import { MUser } from "./MUser";
import { UUID } from "./index";
/* /*
+----------------------------+--------------------------+-----------+ +----------------------------+--------------------------+-----------+

View File

@@ -1,3 +1,17 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { UUID } from "./index"; import { UUID } from "./index";
/* /*

View File

@@ -1,6 +1,21 @@
import { UUID } from "./index"; // Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { MUserEmail } from "./MUserEmail"; import { MUserEmail } from "./MUserEmail";
import { UUID } from "./index";
export interface MUser { export interface MUser {
user_id: UUID<MUser>; user_id: UUID<MUser>;
username: string; // localpart only without @ username: string; // localpart only without @

View File

@@ -1,4 +1,19 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { MUser } from "./MUser"; import { MUser } from "./MUser";
import { UUID } from "./index"; import { UUID } from "./index";
/* /*

View File

@@ -1,4 +1,19 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { MUser } from "./MUser"; import { MUser } from "./MUser";
import { UUID } from "./index"; import { UUID } from "./index";
export interface MUserPassword { export interface MUserPassword {

View File

@@ -1,6 +1,21 @@
import { Id, SynapseUserId } from "./index"; // Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { SRefreshToken } from "./SRefreshToken"; import { SRefreshToken } from "./SRefreshToken";
import { Id, SynapseUserId } from "./index";
/* /*
CREATE TABLE access_tokens ( CREATE TABLE access_tokens (
id bigint NOT NULL, id bigint NOT NULL,

View File

@@ -1,3 +1,17 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { Id, SynapseUserId } from "./index"; import { Id, SynapseUserId } from "./index";
/* /*

View File

@@ -1,3 +1,17 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { SynapseUserId, UnixTimestamp } from "./index"; import { SynapseUserId, UnixTimestamp } from "./index";
export interface SUser { export interface SUser {

View File

@@ -1,3 +1,17 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { SynapseUserId } from "./index"; import { SynapseUserId } from "./index";
export interface SUserExternalId { export interface SUserExternalId {

View File

@@ -1,3 +1,17 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { SynapseUserId } from "./index"; import { SynapseUserId } from "./index";
/* /*

View File

@@ -1,61 +0,0 @@
export interface SynapseOIDCProvider {
idp_id: string;
idp_name: string;
issuer: string;
client_id: string;
scopes: string[];
client_auth_method?: "client_secret_basic" | "client_secret_post" | "none";
client_secret?: string;
client_secret_jwt_key?: string;
}
export interface Sqlite3DatabaseConfig {
name: "sqlite3";
args?: {
database: string;
};
}
export interface Psycopg2DatabaseConfig {
name: "psycopg2";
args?: {
user?: string;
password?: string;
database?: string;
host?: string;
port?: number;
};
}
export type DatabaseConfig = Sqlite3DatabaseConfig | Psycopg2DatabaseConfig | { name?: Omit<string, "sqlite3" | "psycopg2"> };
export interface SynapseConfig {
database?: DatabaseConfig;
oidc_providers?: SynapseOIDCProvider[];
oidc_config?: SynapseOIDCProvider;
allow_guest_access?: boolean;
cas_config?: {
enabled?: boolean;
};
saml2_config?: {
sp_config?: {};
};
sso?: {
client_whitelist?: string[];
update_profile_information?: boolean;
};
jwt_config?: {
enabled?: boolean;
};
password_config?: {
enabled?: boolean;
localdb_enabled?: boolean;
};
enable_registration_captcha?: boolean;
enable_registration?: boolean;
user_consent?: {};
enable_3pid_changes?: boolean;
login_via_existing_session?: {
enabled?: boolean;
};
}

View File

@@ -1,3 +1,17 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
export type UnixTimestamp = number; export type UnixTimestamp = number;
export type SynapseUserId = string; export type SynapseUserId = string;
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars

View File

@@ -1,3 +1,17 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import "knex/types/result"; import "knex/types/result";
declare module "knex/types/result" { declare module "knex/types/result" {

View File

@@ -0,0 +1,15 @@
{
"extends": [
"@tsconfig/strictest/tsconfig.json",
"@tsconfig/node18/tsconfig.json",
],
"compilerOptions": {
"noEmit": true,
"allowJs": true,
},
"include": [
".eslintrc.cjs",
"src/**/*.mts",
"src/**/*.ts"
]
}

View File

@@ -1,16 +1,12 @@
{ {
"extends": [
"@tsconfig/strictest/tsconfig.json",
"@tsconfig/node18/tsconfig.json"
],
"compilerOptions": { "compilerOptions": {
"target": "ES2022", "outDir": "dist",
"module": "ES2022", "rootDir": "src",
"lib": ["ES2022"],
"moduleResolution": "node",
"strict": true,
"sourceMap": true, "sourceMap": true,
"declaration": false, "declaration": false,
"outDir": "./dist",
"rootDir": "./src",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true
} }
} }