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 = {
plugins: [
'matrix-org',
],
root: true,
plugins: ["matrix-org"],
extends: [
'plugin:matrix-org/typescript',
"plugin:prettier/recommended",
"plugin:import/recommended",
"plugin:import/typescript",
"plugin:matrix-org/typescript",
],
env: {
browser: false,
node: true,
},
parser: '@typescript-eslint/parser',
parser: "@typescript-eslint/parser",
parserOptions: {
project: "./tsconfig.json",
project: "./tsconfig.eslint.json",
},
rules: {
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/promise-function-async': 'error',
'@typescript-eslint/await-thenable': 'error',
"matrix-org/require-copyright-header": ["error", HEADER_TEMPLATE],
"import/order": [
"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",
"dev": "ts-node --esm src/index.ts",
"lint": "npm run lint:types && npm run lint:style",
"lint:style": "eslint src",
"lint:types": "tsc --noEmit --skipLibCheck",
"lint:style": "eslint . .eslintrc.cjs",
"lint:types": "tsc --noEmit",
"start": "node dist/index.js"
},
"devDependencies": {
"@tsconfig/node18": "^18.2.2",
"@tsconfig/strictest": "^2.0.2",
"@types/command-line-args": "^5.2.1",
"@types/node": "^18.18.4",
"@typescript-eslint/eslint-plugin": "^6.7.5",
@@ -20,11 +22,13 @@
"eslint": "^8.51.0",
"eslint-config-google": "^0.14.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-prettier": "^5.0.0",
"eslint-plugin-unicorn": "^48.0.1",
"prettier": "^3.0.3",
"ts-node": "^10.9.1",
"ts-node": "github:TypeStrong/ts-node#main",
"typescript": "^5.2.2"
},
"dependencies": {
@@ -35,6 +39,7 @@
"pg": "^8.11.3",
"sqlite3": "^5.1.6",
"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";
import { parse } from "ts-command-line-args";
import log4js from "log4js";
import yaml from "yaml";
import { Knex } from "knex";
// 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 { 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 { SUserThreePid } from "./types/SUserThreePid";
import { SAccessToken } from "./types/SAccessToken";
import { SRefreshToken } from "./types/SRefreshToken";
import {
synapseConfig as synapseConfigSchema,
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");
@@ -19,15 +37,30 @@ interface Options {
help?: boolean;
}
export async function advisor(argv?: string[]): Promise<void> {
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" },
help: { type: Boolean, optional: true, alias: "h", description: "Prints this usage guide" },
},
{
helpArg: "help",
});
export async function advisor(): Promise<void> {
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",
},
help: {
type: Boolean,
optional: true,
alias: "h",
description: "Prints this usage guide",
},
},
{
helpArg: "help",
},
);
const warnings: string[] = [];
function warn(message: string): void {
@@ -42,111 +75,205 @@ export async function advisor(argv?: string[]): Promise<void> {
}
// 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
const synapse = connectToSynapseDatabase(synapseConfig);
async function count(query: Knex.QueryBuilder): Promise<number> {
const res = await (query.first());
const res = await query.first();
if (!res) {
return 0;
}
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) {
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) {
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 (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 {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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");
if (
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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 { DatabaseConfig, Psycopg2DatabaseConfig, Sqlite3DatabaseConfig, SynapseConfig } from "./types/SynapseConfig";
import { MASConfig } from "./types/MASConfig";
import {
MASConfig,
DatabaseConfig as MASDatabaseConfig,
URIDatabaseConfig as MASURIDatabaseConfig,
} from "./schemas/mas.mjs";
import { SynapseConfig } from "./schemas/synapse.mjs";
function isSqlite3(config: DatabaseConfig): config is Sqlite3DatabaseConfig {
return config.name === "sqlite3";
}
function isPsycopg2(config: DatabaseConfig): config is Psycopg2DatabaseConfig {
return config.name === "psycopg2";
}
export function connectToSynapseDatabase({ database }: SynapseConfig): Knex<{}, unknown[]> {
export function connectToSynapseDatabase({
database,
}: SynapseConfig): Knex<{}, unknown[]> {
if (!database) {
throw new Error("Synapse database not configured");
}
if (isSqlite3(database)) {
const filename = database.args?.database;
if (!filename) {
throw new Error("Synapse sqlite3 database not configured");
}
return knex({ client: "sqlite3", connection: { filename }, useNullAsDefault: true });
if (database.name === "sqlite3") {
return knex({
client: "sqlite3",
connection: { filename: database.args.database },
useNullAsDefault: true,
});
}
if (isPsycopg2(database)) {
return knex({ client: "pg", connection: {
user: database?.args?.user,
database: database?.args?.database,
password: database?.args?.password,
port: database?.args?.port,
host: database?.args?.host,
} });
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));
return knex({
client: "pg",
connection,
});
}
const isUriConfig = (
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);
}
throw new Error(`Unsupported database type ${database?.name}. Must be sqlite3 or psycopg2`);
}
export function connectToMASDatabase({ database }: MASConfig): Knex<{}, unknown[]> {
return knex({ client: "pg", connection: database?.uri });
return knex({
client: "pg",
connection,
});
}

View File

@@ -1,8 +1,22 @@
import { ArgumentConfig, parse } from "ts-command-line-args";
import log4js from "log4js";
// 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 log4js from "log4js";
import { ArgumentConfig, parse } from "ts-command-line-args";
import { migrate } from "./migrate.mjs";
import { advisor } from "./advisor.mjs";
import { migrate } from "./migrate.mjs";
log4js.configure({
appenders: {
@@ -21,11 +35,23 @@ interface MainOptions {
}
const mainArgOptions: ArgumentConfig<MainOptions> = {
command: { type: String, description: "Command to run", defaultOption: true, typeLabel: "<advisor|migrate>" },
help: { type: Boolean, optional: true, alias: "h", description: "Prints this usage guide" },
command: {
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 {
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 { parse } from "ts-command-line-args";
import id128 from "id128";
import log4js from "log4js";
import { parse } from "ts-command-line-args";
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 { 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");
@@ -32,19 +47,44 @@ interface MigrationOptions {
help?: boolean;
}
export async function migrate(argv?: string[]): Promise<void> {
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" },
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>" },
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",
argv,
});
export async function migrate(): Promise<void> {
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",
},
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>",
},
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",
},
);
const warnings: string[] = [];
function warn(message: string): void {
@@ -62,35 +102,56 @@ export async function migrate(argv?: string[]): Promise<void> {
}
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
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);
// 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) {
log.fatal("Missing database URI in MAS config");
process.exit(1);
}
// connect to MAS database
const mas = connectToMASDatabase(masConfig);
const upstreamProviders = new Map<string, MUpstreamOauthProvider>();
for (const mapping of args.upstreamProviderMapping) {
const [providerId, masProviderId] = mapping.split(":");
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}`);
if (!providerId || !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) {
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);
}
@@ -98,15 +159,23 @@ export async function migrate(argv?: string[]): Promise<void> {
function stringifyAndRedact(input: unknown): string {
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) {
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");
@@ -136,116 +205,184 @@ export async function migrate(argv?: string[]): Promise<void> {
log.debug(`${stringifyAndRedact(user)} => ${stringifyAndRedact(masUser)}`);
// users.password_hash => user_passwords
if (user.password_hash) {
const masUserPassword: MUserPassword = {
user_password_id: makeUuid(),
const masUserPassword: MUserPassword = {
user_password_id: makeUuid(),
user_id: masUser.user_id,
hashed_password: user.password_hash,
created_at: masUser.created_at, // TODO: should we use now() instead of created_at?
version: 1,
};
log.debug(
`Password ${user.password_hash.slice(-4)} => ${stringifyAndRedact(
masUserPassword,
)}`,
);
executions.push(() => mas.insert(masUserPassword).into("user_passwords"));
}
// user_threepids => user_emails
let primaryEmail: MUserEmail | undefined;
const synapseThreePids = await synapse
.select("*")
.from<SUserThreePid>("user_threepids")
.where({ user_id: user.name });
for (const threePid of synapseThreePids) {
if (threePid.medium !== "email") {
warningsForUser += 1;
warn(
`Skipping non-email 3pid ${threePid.medium} for user ${user.name}`,
);
continue;
}
const masUserEmail: MUserEmail = {
user_email_id: makeUuid(),
user_id: masUser.user_id,
email: threePid.address.toLowerCase(),
created_at: new Date(parseInt(`${threePid.added_at}`) * 1000),
};
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) {
primaryEmail = masUserEmail;
}
executions.push(() => mas.insert(masUserEmail).into("user_emails"));
}
if (primaryEmail) {
log.debug(
`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
const synapseExternalIds = await synapse
.select("*")
.from<SUserExternalId>("user_external_ids")
.where({ user_id: user.name });
for (const externalId of synapseExternalIds) {
try {
if (!upstreamProviders.has(externalId.auth_provider)) {
throw new Error(
`Unknown upstream provider ${externalId.auth_provider}`,
);
}
const provider = upstreamProviders.get(externalId.auth_provider)!;
const masUpstreamOauthLink: MUpstreamOauthLink = {
upstream_oauth_link_id: makeUuid(),
user_id: masUser.user_id,
hashed_password: user.password_hash,
created_at: masUser.created_at, // TODO: should we use now() instead of created_at?
version: 1,
upstream_oauth_provider_id: provider.upstream_oauth_provider_id,
subject: externalId.external_id,
created_at: masUser.created_at,
};
log.debug(`Password ${user.password_hash.slice(-4)} => ${stringifyAndRedact(masUserPassword)}`);
executions.push(() => mas.insert(masUserPassword).into("user_passwords"));
log.debug(
`${stringifyAndRedact(synapseExternalIds)} => ${stringifyAndRedact(
masUpstreamOauthLink,
)}`,
);
executions.push(() =>
mas.insert(masUpstreamOauthLink).into("upstream_oauth_links"),
);
} catch (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
const synapseAccessTokens = await synapse
.select("*")
.from<SAccessToken>("access_tokens")
.where({ user_id: user.name });
for (const accessToken of synapseAccessTokens) {
if (!accessToken.device_id) {
warningsForUser += 1;
warn(
`Skipping access token ${accessToken.token} for user ${user.name} with no device_id`,
);
continue;
}
// user_threepids => user_emails
let primaryEmail: MUserEmail | undefined;
const synapseThreePids = await synapse.select("*").from<SUserThreePid>("user_threepids").where({ user_id: user.name });
for (const threePid of synapseThreePids) {
if (threePid.medium !== "email") {
warningsForUser += 1;
warn(`Skipping non-email 3pid ${threePid.medium} for user ${user.name}`);
continue;
}
const masUserEmail: MUserEmail = {
user_email_id: makeUuid(),
user_id: masUser.user_id,
email: threePid.address.toLowerCase(),
created_at: new Date(parseInt(`${threePid.added_at}`) * 1000),
confirmed_at: threePid.validated_at ? new Date(parseInt(`${threePid.validated_at}`) * 1000) : undefined,
};
const masCompatSession: MCompatSession = {
compat_session_id: makeUuid(),
user_id: masUser.user_id,
device_id: accessToken.device_id,
created_at: accessToken.last_validated
? new Date(parseInt(`${accessToken.last_validated}`))
: masUser.created_at,
is_synapse_admin: user.admin === 1,
};
log.debug(
`${stringifyAndRedact(accessToken)} => ${stringifyAndRedact(
masCompatSession,
)}`,
);
executions.push(() =>
mas.insert(masCompatSession).into("compat_sessions"),
);
log.debug(`${stringifyAndRedact(threePid)} => ${stringifyAndRedact(masUserEmail)}`);
if (!primaryEmail && threePid.validated_at) {
primaryEmail = masUserEmail;
}
executions.push(() => mas.insert(masUserEmail).into("user_emails"));
}
if (primaryEmail) {
log.debug(`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 }));
}
const masCompatAccessToken: MCompatAccessToken = {
compat_access_token_id: makeUuid(),
compat_session_id: masCompatSession.compat_session_id,
access_token: accessToken.token,
created_at: masCompatSession.created_at,
};
log.debug(
`Access token ${accessToken.id} => ${stringifyAndRedact(
masCompatAccessToken,
)}`,
);
executions.push(() =>
mas.insert(masCompatAccessToken).into("compat_access_tokens"),
);
// user_external_ids => upstream_oauth_links
const synapseExternalIds = await synapse.select("*").from<SUserExternalId>("user_external_ids").where({ user_id: user.name });
for (const externalId of synapseExternalIds) {
try {
if (!upstreamProviders.has(externalId.auth_provider)) {
throw new Error(`Unknown upstream provider ${externalId.auth_provider}`);
}
const provider = upstreamProviders.get(externalId.auth_provider)!;
const masUpstreamOauthLink: MUpstreamOauthLink = {
upstream_oauth_link_id: makeUuid(),
user_id: masUser.user_id,
upstream_oauth_provider_id: provider.upstream_oauth_provider_id,
subject: externalId.external_id,
created_at: masUser.created_at,
if (accessToken.refresh_token_id) {
const synapseRefreshToken = await synapse
.select("*")
.from<SRefreshToken>("refresh_tokens")
.where({ id: accessToken.refresh_token_id })
.first();
if (synapseRefreshToken) {
const masCompatRefreshToken: MCompatRefreshToken = {
compat_refresh_token_id: makeUuid(),
compat_session_id: masCompatSession.compat_session_id,
compat_access_token_id: masCompatAccessToken.compat_access_token_id,
refresh_token: synapseRefreshToken.token,
created_at: masCompatSession.created_at,
};
log.debug(`${stringifyAndRedact(synapseExternalIds)} => ${stringifyAndRedact(masUpstreamOauthLink)}`);
executions.push(() => mas.insert(masUpstreamOauthLink).into("upstream_oauth_links"));
} catch (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
const synapseAccessTokens = await synapse.select("*").from<SAccessToken>("access_tokens").where({ user_id: user.name });
for (const accessToken of synapseAccessTokens) {
if (!accessToken.device_id) {
log.debug(
`Refresh token ${synapseRefreshToken.id} => ${stringifyAndRedact(
masCompatRefreshToken,
)}`,
);
executions.push(() =>
mas.insert(masCompatRefreshToken).into("compat_refresh_tokens"),
);
} else {
warningsForUser += 1;
warn(`Skipping access token ${accessToken.token} for user ${user.name} with no device_id`);
continue;
}
const masCompatSession: MCompatSession = {
compat_session_id: makeUuid(),
user_id: masUser.user_id,
device_id: accessToken.device_id,
created_at: accessToken.last_validated ? new Date(parseInt(`${accessToken.last_validated}`)) : masUser.created_at,
is_synapse_admin: user.admin === 1,
};
log.debug(`${stringifyAndRedact(accessToken)} => ${stringifyAndRedact(masCompatSession)}`);
executions.push(() => mas.insert(masCompatSession).into("compat_sessions"));
const masCompatAccessToken: MCompatAccessToken = {
compat_access_token_id: makeUuid(),
compat_session_id: masCompatSession.compat_session_id,
access_token: accessToken.token,
created_at: masCompatSession.created_at,
};
log.debug(`Access token ${accessToken.id} => ${stringifyAndRedact(masCompatAccessToken)}`);
executions.push(() => mas.insert(masCompatAccessToken).into("compat_access_tokens"));
if (accessToken.refresh_token_id) {
const synapseRefreshToken = await synapse.select("*").from<SRefreshToken>("refresh_tokens").where({ id: accessToken.refresh_token_id }).first();
if (synapseRefreshToken) {
const masCompatRefreshToken: MCompatRefreshToken = {
compat_refresh_token_id: makeUuid(),
compat_session_id: masCompatSession.compat_session_id,
compat_access_token_id: masCompatAccessToken.compat_access_token_id,
refresh_token: synapseRefreshToken.token,
created_at: masCompatSession.created_at,
};
log.debug(`Refresh token ${synapseRefreshToken.id} => ${stringifyAndRedact(masCompatRefreshToken)}`);
executions.push(() => mas.insert(masCompatRefreshToken).into("compat_refresh_tokens"));
} else {
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}`,
);
}
}
}
if (warningsForUser > 0) {
if (!args.dryRun) {
@@ -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));
if (fatals > 0) {
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 { UUID } from "./index";
/*
+------------------------+--------------------------+-----------+
| 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 { UUID } from "./index";
import { MCompatSession } from "./MCompatSession";
import { UUID } from "./index";
/*
+-------------------------+--------------------------+-----------+
| 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 { UUID } from "./index";
/*

View File

@@ -1,6 +1,21 @@
import { MUser } from "./MUser";
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 { 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";
/*

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 { UUID } from "./index";
export interface MUser {
user_id: UUID<MUser>;
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 { 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 { UUID } from "./index";
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 { Id, SynapseUserId } from "./index";
/*
CREATE TABLE access_tokens (
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";
/*

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";
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";
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";
/*

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 SynapseUserId = string;
// eslint-disable-next-line @typescript-eslint/no-unused-vars

View File

@@ -1,7 +1,21 @@
// 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";
declare module "knex/types/result" {
interface Registry {
Count: number;
Count: number;
}
}

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": {
"target": "ES2022",
"module": "ES2022",
"lib": ["ES2022"],
"moduleResolution": "node",
"strict": true,
"sourceMap": true,
"declaration": false,
"outDir": "./dist",
"rootDir": "./src",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true
"outDir": "dist",
"rootDir": "src",
"sourceMap": true,
"declaration": false,
}
}