You've already forked authentication-service
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:
@@ -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"],
|
||||||
};
|
};
|
||||||
|
|||||||
3281
tools/syn2mas/package-lock.json
generated
3281
tools/syn2mas/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
helpArg: "help",
|
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[] = [];
|
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",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPsycopg2(database)) {
|
const connection: Knex.PgConnectionConfig = {};
|
||||||
return knex({ client: "pg", connection: {
|
database.args.database && (connection.database = database.args.database);
|
||||||
user: database?.args?.user,
|
database.args.user && (connection.user = database.args.user);
|
||||||
database: database?.args?.database,
|
database.args.password && (connection.password = database.args.password);
|
||||||
password: database?.args?.password,
|
typeof database.args.port === "number" &&
|
||||||
port: database?.args?.port,
|
(connection.port = database.args.port);
|
||||||
host: database?.args?.host,
|
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`);
|
return knex({
|
||||||
}
|
client: "pg",
|
||||||
|
connection,
|
||||||
export function connectToMASDatabase({ database }: MASConfig): Knex<{}, unknown[]> {
|
});
|
||||||
return knex({ client: "pg", connection: database?.uri });
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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") {
|
||||||
|
|||||||
@@ -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: {
|
||||||
masConfigFile: { type: String, description: "Path to MAS config.yaml" },
|
type: String,
|
||||||
upstreamProviderMapping: { type: String, defaultValue: [], multiple: true, description: "Mapping of upstream provider IDs to MAS provider IDs. Format: <upstream_provider_id>:<mas_provider_id>" },
|
description: "Command to run",
|
||||||
dryRun: { type: Boolean, optional: true, defaultValue: false, description: "Dry run only, do not write to database" },
|
defaultOption: true,
|
||||||
help: { type: Boolean, optional: true, alias: "h", description: "Prints this usage guide" },
|
typeLabel: "migrate",
|
||||||
},
|
},
|
||||||
{
|
synapseConfigFile: {
|
||||||
helpArg: "help",
|
type: String,
|
||||||
argv,
|
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[] = [];
|
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");
|
||||||
@@ -136,116 +205,184 @@ export async function migrate(argv?: string[]): Promise<void> {
|
|||||||
log.debug(`${stringifyAndRedact(user)} => ${stringifyAndRedact(masUser)}`);
|
log.debug(`${stringifyAndRedact(user)} => ${stringifyAndRedact(masUser)}`);
|
||||||
// users.password_hash => user_passwords
|
// users.password_hash => user_passwords
|
||||||
if (user.password_hash) {
|
if (user.password_hash) {
|
||||||
const masUserPassword: MUserPassword = {
|
const masUserPassword: MUserPassword = {
|
||||||
user_password_id: makeUuid(),
|
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,
|
user_id: masUser.user_id,
|
||||||
hashed_password: user.password_hash,
|
upstream_oauth_provider_id: provider.upstream_oauth_provider_id,
|
||||||
created_at: masUser.created_at, // TODO: should we use now() instead of created_at?
|
subject: externalId.external_id,
|
||||||
version: 1,
|
created_at: masUser.created_at,
|
||||||
};
|
};
|
||||||
|
|
||||||
log.debug(`Password ${user.password_hash.slice(-4)} => ${stringifyAndRedact(masUserPassword)}`);
|
log.debug(
|
||||||
executions.push(() => mas.insert(masUserPassword).into("user_passwords"));
|
`${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
|
const masCompatSession: MCompatSession = {
|
||||||
let primaryEmail: MUserEmail | undefined;
|
compat_session_id: makeUuid(),
|
||||||
const synapseThreePids = await synapse.select("*").from<SUserThreePid>("user_threepids").where({ user_id: user.name });
|
user_id: masUser.user_id,
|
||||||
for (const threePid of synapseThreePids) {
|
device_id: accessToken.device_id,
|
||||||
if (threePid.medium !== "email") {
|
created_at: accessToken.last_validated
|
||||||
warningsForUser += 1;
|
? new Date(parseInt(`${accessToken.last_validated}`))
|
||||||
warn(`Skipping non-email 3pid ${threePid.medium} for user ${user.name}`);
|
: masUser.created_at,
|
||||||
continue;
|
is_synapse_admin: user.admin === 1,
|
||||||
}
|
};
|
||||||
const masUserEmail: MUserEmail = {
|
log.debug(
|
||||||
user_email_id: makeUuid(),
|
`${stringifyAndRedact(accessToken)} => ${stringifyAndRedact(
|
||||||
user_id: masUser.user_id,
|
masCompatSession,
|
||||||
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,
|
executions.push(() =>
|
||||||
};
|
mas.insert(masCompatSession).into("compat_sessions"),
|
||||||
|
);
|
||||||
|
|
||||||
log.debug(`${stringifyAndRedact(threePid)} => ${stringifyAndRedact(masUserEmail)}`);
|
const masCompatAccessToken: MCompatAccessToken = {
|
||||||
if (!primaryEmail && threePid.validated_at) {
|
compat_access_token_id: makeUuid(),
|
||||||
primaryEmail = masUserEmail;
|
compat_session_id: masCompatSession.compat_session_id,
|
||||||
}
|
access_token: accessToken.token,
|
||||||
executions.push(() => mas.insert(masUserEmail).into("user_emails"));
|
created_at: masCompatSession.created_at,
|
||||||
}
|
};
|
||||||
if (primaryEmail) {
|
log.debug(
|
||||||
log.debug(`Setting primary email for existing user ${masUser.username} to ${primaryEmail.email} as update`);
|
`Access token ${accessToken.id} => ${stringifyAndRedact(
|
||||||
executions.push(() => mas("users").where({ user_id: masUser!.user_id }).update({ primary_user_email_id: primaryEmail!.user_email_id }));
|
masCompatAccessToken,
|
||||||
}
|
)}`,
|
||||||
|
);
|
||||||
|
executions.push(() =>
|
||||||
|
mas.insert(masCompatAccessToken).into("compat_access_tokens"),
|
||||||
|
);
|
||||||
|
|
||||||
// user_external_ids => upstream_oauth_links
|
if (accessToken.refresh_token_id) {
|
||||||
const synapseExternalIds = await synapse.select("*").from<SUserExternalId>("user_external_ids").where({ user_id: user.name });
|
const synapseRefreshToken = await synapse
|
||||||
for (const externalId of synapseExternalIds) {
|
.select("*")
|
||||||
try {
|
.from<SRefreshToken>("refresh_tokens")
|
||||||
if (!upstreamProviders.has(externalId.auth_provider)) {
|
.where({ id: accessToken.refresh_token_id })
|
||||||
throw new Error(`Unknown upstream provider ${externalId.auth_provider}`);
|
.first();
|
||||||
}
|
if (synapseRefreshToken) {
|
||||||
const provider = upstreamProviders.get(externalId.auth_provider)!;
|
const masCompatRefreshToken: MCompatRefreshToken = {
|
||||||
const masUpstreamOauthLink: MUpstreamOauthLink = {
|
compat_refresh_token_id: makeUuid(),
|
||||||
upstream_oauth_link_id: makeUuid(),
|
compat_session_id: masCompatSession.compat_session_id,
|
||||||
user_id: masUser.user_id,
|
compat_access_token_id: masCompatAccessToken.compat_access_token_id,
|
||||||
upstream_oauth_provider_id: provider.upstream_oauth_provider_id,
|
refresh_token: synapseRefreshToken.token,
|
||||||
subject: externalId.external_id,
|
created_at: masCompatSession.created_at,
|
||||||
created_at: masUser.created_at,
|
|
||||||
};
|
};
|
||||||
|
log.debug(
|
||||||
log.debug(`${stringifyAndRedact(synapseExternalIds)} => ${stringifyAndRedact(masUpstreamOauthLink)}`);
|
`Refresh token ${synapseRefreshToken.id} => ${stringifyAndRedact(
|
||||||
|
masCompatRefreshToken,
|
||||||
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}`);
|
executions.push(() =>
|
||||||
}
|
mas.insert(masCompatRefreshToken).into("compat_refresh_tokens"),
|
||||||
}
|
);
|
||||||
|
} else {
|
||||||
// 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;
|
warningsForUser += 1;
|
||||||
warn(`Skipping access token ${accessToken.token} for user ${user.name} with no device_id`);
|
warn(
|
||||||
continue;
|
`Unable to locate refresh token ${accessToken.refresh_token_id} for user ${user.name}`,
|
||||||
}
|
);
|
||||||
|
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (warningsForUser > 0) {
|
if (warningsForUser > 0) {
|
||||||
if (!args.dryRun) {
|
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));
|
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`);
|
||||||
|
|||||||
44
tools/syn2mas/src/schemas/mas.mts
Normal file
44
tools/syn2mas/src/schemas/mas.mts
Normal 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>;
|
||||||
99
tools/syn2mas/src/schemas/synapse.mts
Normal file
99
tools/syn2mas/src/schemas/synapse.mts
Normal 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>;
|
||||||
8
tools/syn2mas/src/types/MASConfig.d.ts
vendored
8
tools/syn2mas/src/types/MASConfig.d.ts
vendored
@@ -1,8 +0,0 @@
|
|||||||
export interface MASConfig {
|
|
||||||
database?: {
|
|
||||||
uri?: string;
|
|
||||||
};
|
|
||||||
secrets?: {
|
|
||||||
encryption?: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
17
tools/syn2mas/src/types/MCompatAccessToken.d.ts
vendored
17
tools/syn2mas/src/types/MCompatAccessToken.d.ts
vendored
@@ -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 |
|
||||||
|
|||||||
17
tools/syn2mas/src/types/MCompatRefreshToken.d.ts
vendored
17
tools/syn2mas/src/types/MCompatRefreshToken.d.ts
vendored
@@ -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 |
|
||||||
|
|||||||
15
tools/syn2mas/src/types/MCompatSession.d.ts
vendored
15
tools/syn2mas/src/types/MCompatSession.d.ts
vendored
@@ -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";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
19
tools/syn2mas/src/types/MUpstreamOauthLink.d.ts
vendored
19
tools/syn2mas/src/types/MUpstreamOauthLink.d.ts
vendored
@@ -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";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
+----------------------------+--------------------------+-----------+
|
+----------------------------+--------------------------+-----------+
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
17
tools/syn2mas/src/types/MUser.d.ts
vendored
17
tools/syn2mas/src/types/MUser.d.ts
vendored
@@ -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 @
|
||||||
|
|||||||
15
tools/syn2mas/src/types/MUserEmail.d.ts
vendored
15
tools/syn2mas/src/types/MUserEmail.d.ts
vendored
@@ -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";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
15
tools/syn2mas/src/types/MUserPassword.d.ts
vendored
15
tools/syn2mas/src/types/MUserPassword.d.ts
vendored
@@ -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 {
|
||||||
|
|||||||
17
tools/syn2mas/src/types/SAccessToken.d.ts
vendored
17
tools/syn2mas/src/types/SAccessToken.d.ts
vendored
@@ -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,
|
||||||
|
|||||||
14
tools/syn2mas/src/types/SRefreshToken.d.ts
vendored
14
tools/syn2mas/src/types/SRefreshToken.d.ts
vendored
@@ -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";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
14
tools/syn2mas/src/types/SUser.d.ts
vendored
14
tools/syn2mas/src/types/SUser.d.ts
vendored
@@ -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 {
|
||||||
|
|||||||
14
tools/syn2mas/src/types/SUserExternalId.d.ts
vendored
14
tools/syn2mas/src/types/SUserExternalId.d.ts
vendored
@@ -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 {
|
||||||
|
|||||||
14
tools/syn2mas/src/types/SUserThreePid.d.ts
vendored
14
tools/syn2mas/src/types/SUserThreePid.d.ts
vendored
@@ -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";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
61
tools/syn2mas/src/types/SynapseConfig.d.ts
vendored
61
tools/syn2mas/src/types/SynapseConfig.d.ts
vendored
@@ -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;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
14
tools/syn2mas/src/types/index.d.ts
vendored
14
tools/syn2mas/src/types/index.d.ts
vendored
@@ -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
|
||||||
|
|||||||
16
tools/syn2mas/src/types/knex.d.ts
vendored
16
tools/syn2mas/src/types/knex.d.ts
vendored
@@ -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";
|
import "knex/types/result";
|
||||||
|
|
||||||
declare module "knex/types/result" {
|
declare module "knex/types/result" {
|
||||||
interface Registry {
|
interface Registry {
|
||||||
Count: number;
|
Count: number;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
tools/syn2mas/tsconfig.eslint.json
Normal file
15
tools/syn2mas/tsconfig.eslint.json
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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"],
|
"sourceMap": true,
|
||||||
"moduleResolution": "node",
|
"declaration": false,
|
||||||
"strict": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"declaration": false,
|
|
||||||
"outDir": "./dist",
|
|
||||||
"rootDir": "./src",
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"skipLibCheck": true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user