diff --git a/crates/assets/js/admin/proto/config.ts b/crates/assets/js/admin/proto/config.ts index 96bb2ad5..f5210275 100644 --- a/crates/assets/js/admin/proto/config.ts +++ b/crates/assets/js/admin/proto/config.ts @@ -1,6 +1,6 @@ // Code generated by protoc-gen-ts_proto. DO NOT EDIT. // versions: -// protoc-gen-ts_proto v2.7.0 +// protoc-gen-ts_proto v2.7.7 // protoc v3.21.12 // source: config.proto @@ -9,6 +9,51 @@ import { BinaryReader, BinaryWriter } from "@bufbuild/protobuf/wire"; export const protobufPackage = "config"; +export enum SmtpEncryption { + SMTP_ENCRYPTION_UNDEFINED = 0, + SMTP_ENCRYPTION_NONE = 1, + SMTP_ENCRYPTION_STARTTLS = 2, + SMTP_ENCRYPTION_TLS = 3, + UNRECOGNIZED = -1, +} + +export function smtpEncryptionFromJSON(object: any): SmtpEncryption { + switch (object) { + case 0: + case "SMTP_ENCRYPTION_UNDEFINED": + return SmtpEncryption.SMTP_ENCRYPTION_UNDEFINED; + case 1: + case "SMTP_ENCRYPTION_NONE": + return SmtpEncryption.SMTP_ENCRYPTION_NONE; + case 2: + case "SMTP_ENCRYPTION_STARTTLS": + return SmtpEncryption.SMTP_ENCRYPTION_STARTTLS; + case 3: + case "SMTP_ENCRYPTION_TLS": + return SmtpEncryption.SMTP_ENCRYPTION_TLS; + case -1: + case "UNRECOGNIZED": + default: + return SmtpEncryption.UNRECOGNIZED; + } +} + +export function smtpEncryptionToJSON(object: SmtpEncryption): string { + switch (object) { + case SmtpEncryption.SMTP_ENCRYPTION_UNDEFINED: + return "SMTP_ENCRYPTION_UNDEFINED"; + case SmtpEncryption.SMTP_ENCRYPTION_NONE: + return "SMTP_ENCRYPTION_NONE"; + case SmtpEncryption.SMTP_ENCRYPTION_STARTTLS: + return "SMTP_ENCRYPTION_STARTTLS"; + case SmtpEncryption.SMTP_ENCRYPTION_TLS: + return "SMTP_ENCRYPTION_TLS"; + case SmtpEncryption.UNRECOGNIZED: + default: + return "UNRECOGNIZED"; + } +} + export enum OAuthProviderId { OAUTH_PROVIDER_ID_UNDEFINED = 0, TEST = 1, @@ -285,7 +330,11 @@ export interface EmailConfig { smtpHost?: string | undefined; smtpPort?: number | undefined; smtpUsername?: string | undefined; - smtpPassword?: string | undefined; + smtpPassword?: + | string + | undefined; + /** Which encryption method to use. STARTTLS by default. */ + smtpEncryption?: SmtpEncryption | undefined; senderName?: string | undefined; senderAddress?: string | undefined; userVerificationTemplate?: EmailTemplate | undefined; @@ -340,6 +389,13 @@ export interface AuthConfig { | undefined; /** / Map of configured OAuth providers. */ oauthProviders: { [key: string]: OAuthProviderConfig }; + /** + * / List of custom URI schemes allowed as auth redirects. + * / + * / This is useful for mobile apps, desktop or SPAs where an app registers a + * / custom scheme for calls it wants to handle. + */ + customUriSchemes: string[]; } export interface AuthConfig_OauthProvidersEntry { @@ -385,7 +441,11 @@ export interface ServerConfig { | number | undefined; /** / If present will use S3 setup over local file-system based storage. */ - s3StorageConfig?: S3StorageConfig | undefined; + s3StorageConfig?: + | S3StorageConfig + | undefined; + /** / If enabled, batches of transactions can be submitted for attomic execution */ + enableRecordTransactions?: boolean | undefined; } export interface SystemJob { @@ -393,7 +453,10 @@ export interface SystemJob { id?: | SystemJobId | undefined; - /** / Cron spec: shorthand or 7-components: (sec, min, hour, day of month, / month, day of week, year). */ + /** + * / Cron spec: shorthand or 7-components: (sec, min, hour, day of month, / + * / month, day of week, year). + */ schedule?: | string | undefined; @@ -468,7 +531,8 @@ export interface RecordApiConfig { * / matches the current authenticated user's id. One can also construct * / arbitrary validations including sub-queries, e.g.: * / - * / _USER_.id = _REQ_.owner AND EXISTS(SELECT FROM allowed WHERE allowed.user = _USER_.id) + * / _USER_.id = _REQ_.owner AND EXISTS(SELECT FROM allowed WHERE + * / allowed.user = _USER_.id) */ createAccessRule?: string | undefined; readAccessRule?: string | undefined; @@ -486,6 +550,8 @@ export interface RecordApiConfig { * / allowed to be expanded. */ expand: string[]; + /** / Hard limit for listing records (default: 1024). */ + listingHardLimit?: number | undefined; } export interface JsonSchemaConfig { @@ -524,7 +590,7 @@ export const EmailTemplate: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): EmailTemplate { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseEmailTemplate(); while (reader.pos < end) { const tag = reader.uint32(); @@ -601,6 +667,9 @@ export const EmailConfig: MessageFns = { if (message.smtpPassword !== undefined && message.smtpPassword !== "") { writer.uint32(34).string(message.smtpPassword); } + if (message.smtpEncryption !== undefined && message.smtpEncryption !== 0) { + writer.uint32(40).int32(message.smtpEncryption); + } if (message.senderName !== undefined && message.senderName !== "") { writer.uint32(90).string(message.senderName); } @@ -621,7 +690,7 @@ export const EmailConfig: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): EmailConfig { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseEmailConfig(); while (reader.pos < end) { const tag = reader.uint32(); @@ -658,6 +727,14 @@ export const EmailConfig: MessageFns = { message.smtpPassword = reader.string(); continue; } + case 5: { + if (tag !== 40) { + break; + } + + message.smtpEncryption = reader.int32() as any; + continue; + } case 11: { if (tag !== 90) { break; @@ -713,6 +790,7 @@ export const EmailConfig: MessageFns = { smtpPort: isSet(object.smtpPort) ? globalThis.Number(object.smtpPort) : undefined, smtpUsername: isSet(object.smtpUsername) ? globalThis.String(object.smtpUsername) : undefined, smtpPassword: isSet(object.smtpPassword) ? globalThis.String(object.smtpPassword) : undefined, + smtpEncryption: isSet(object.smtpEncryption) ? smtpEncryptionFromJSON(object.smtpEncryption) : undefined, senderName: isSet(object.senderName) ? globalThis.String(object.senderName) : undefined, senderAddress: isSet(object.senderAddress) ? globalThis.String(object.senderAddress) : undefined, userVerificationTemplate: isSet(object.userVerificationTemplate) @@ -741,6 +819,9 @@ export const EmailConfig: MessageFns = { if (message.smtpPassword !== undefined && message.smtpPassword !== "") { obj.smtpPassword = message.smtpPassword; } + if (message.smtpEncryption !== undefined && message.smtpEncryption !== 0) { + obj.smtpEncryption = smtpEncryptionToJSON(message.smtpEncryption); + } if (message.senderName !== undefined && message.senderName !== "") { obj.senderName = message.senderName; } @@ -768,6 +849,7 @@ export const EmailConfig: MessageFns = { message.smtpPort = object.smtpPort ?? 0; message.smtpUsername = object.smtpUsername ?? ""; message.smtpPassword = object.smtpPassword ?? ""; + message.smtpEncryption = object.smtpEncryption ?? 0; message.senderName = object.senderName ?? ""; message.senderAddress = object.senderAddress ?? ""; message.userVerificationTemplate = @@ -817,7 +899,7 @@ export const OAuthProviderConfig: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): OAuthProviderConfig { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseOAuthProviderConfig(); while (reader.pos < end) { const tag = reader.uint32(); @@ -942,7 +1024,7 @@ export const OAuthProviderConfig: MessageFns = { }; function createBaseAuthConfig(): AuthConfig { - return { oauthProviders: {} }; + return { oauthProviders: {}, customUriSchemes: [] }; } export const AuthConfig: MessageFns = { @@ -977,12 +1059,15 @@ export const AuthConfig: MessageFns = { Object.entries(message.oauthProviders).forEach(([key, value]) => { AuthConfig_OauthProvidersEntry.encode({ key: key as any, value }, writer.uint32(90).fork()).join(); }); + for (const v of message.customUriSchemes) { + writer.uint32(170).string(v!); + } return writer; }, decode(input: BinaryReader | Uint8Array, length?: number): AuthConfig { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseAuthConfig(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1054,6 +1139,14 @@ export const AuthConfig: MessageFns = { } continue; } + case 21: { + if (tag !== 170) { + break; + } + + message.customUriSchemes.push(reader.string()); + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -1088,6 +1181,9 @@ export const AuthConfig: MessageFns = { return acc; }, {}) : {}, + customUriSchemes: globalThis.Array.isArray(object?.customUriSchemes) + ? object.customUriSchemes.map((e: any) => globalThis.String(e)) + : [], }; }, @@ -1129,6 +1225,9 @@ export const AuthConfig: MessageFns = { }); } } + if (message.customUriSchemes?.length) { + obj.customUriSchemes = message.customUriSchemes; + } return obj; }, @@ -1153,6 +1252,7 @@ export const AuthConfig: MessageFns = { }, {}, ); + message.customUriSchemes = object.customUriSchemes?.map((e) => e) || []; return message; }, }; @@ -1174,7 +1274,7 @@ export const AuthConfig_OauthProvidersEntry: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): S3StorageConfig { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseS3StorageConfig(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1379,12 +1479,15 @@ export const ServerConfig: MessageFns = { if (message.s3StorageConfig !== undefined) { S3StorageConfig.encode(message.s3StorageConfig, writer.uint32(106).fork()).join(); } + if (message.enableRecordTransactions !== undefined && message.enableRecordTransactions !== false) { + writer.uint32(112).bool(message.enableRecordTransactions); + } return writer; }, decode(input: BinaryReader | Uint8Array, length?: number): ServerConfig { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseServerConfig(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1421,6 +1524,14 @@ export const ServerConfig: MessageFns = { message.s3StorageConfig = S3StorageConfig.decode(reader, reader.uint32()); continue; } + case 14: { + if (tag !== 112) { + break; + } + + message.enableRecordTransactions = reader.bool(); + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -1436,6 +1547,9 @@ export const ServerConfig: MessageFns = { siteUrl: isSet(object.siteUrl) ? globalThis.String(object.siteUrl) : undefined, logsRetentionSec: isSet(object.logsRetentionSec) ? globalThis.Number(object.logsRetentionSec) : undefined, s3StorageConfig: isSet(object.s3StorageConfig) ? S3StorageConfig.fromJSON(object.s3StorageConfig) : undefined, + enableRecordTransactions: isSet(object.enableRecordTransactions) + ? globalThis.Boolean(object.enableRecordTransactions) + : undefined, }; }, @@ -1453,6 +1567,9 @@ export const ServerConfig: MessageFns = { if (message.s3StorageConfig !== undefined) { obj.s3StorageConfig = S3StorageConfig.toJSON(message.s3StorageConfig); } + if (message.enableRecordTransactions !== undefined && message.enableRecordTransactions !== false) { + obj.enableRecordTransactions = message.enableRecordTransactions; + } return obj; }, @@ -1467,6 +1584,7 @@ export const ServerConfig: MessageFns = { message.s3StorageConfig = (object.s3StorageConfig !== undefined && object.s3StorageConfig !== null) ? S3StorageConfig.fromPartial(object.s3StorageConfig) : undefined; + message.enableRecordTransactions = object.enableRecordTransactions ?? false; return message; }, }; @@ -1491,7 +1609,7 @@ export const SystemJob: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): SystemJob { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseSystemJob(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1577,7 +1695,7 @@ export const JobsConfig: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): JobsConfig { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseJobsConfig(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1646,16 +1764,12 @@ export const RecordApiConfig: MessageFns = { if (message.enableSubscriptions !== undefined && message.enableSubscriptions !== false) { writer.uint32(72).bool(message.enableSubscriptions); } - writer.uint32(58).fork(); for (const v of message.aclWorld) { - writer.int32(v); + writer.uint32(56).int32(v!); } - writer.join(); - writer.uint32(66).fork(); for (const v of message.aclAuthenticated) { - writer.int32(v); + writer.uint32(64).int32(v!); } - writer.join(); for (const v of message.excludedColumns) { writer.uint32(82).string(v!); } @@ -1677,12 +1791,15 @@ export const RecordApiConfig: MessageFns = { for (const v of message.expand) { writer.uint32(170).string(v!); } + if (message.listingHardLimit !== undefined && message.listingHardLimit !== 0) { + writer.uint32(176).uint64(message.listingHardLimit); + } return writer; }, decode(input: BinaryReader | Uint8Array, length?: number): RecordApiConfig { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseRecordApiConfig(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1819,6 +1936,14 @@ export const RecordApiConfig: MessageFns = { message.expand.push(reader.string()); continue; } + case 22: { + if (tag !== 176) { + break; + } + + message.listingHardLimit = longToNumber(reader.uint64()); + continue; + } } if ((tag & 7) === 4 || tag === 0) { break; @@ -1856,6 +1981,7 @@ export const RecordApiConfig: MessageFns = { deleteAccessRule: isSet(object.deleteAccessRule) ? globalThis.String(object.deleteAccessRule) : undefined, schemaAccessRule: isSet(object.schemaAccessRule) ? globalThis.String(object.schemaAccessRule) : undefined, expand: globalThis.Array.isArray(object?.expand) ? object.expand.map((e: any) => globalThis.String(e)) : [], + listingHardLimit: isSet(object.listingHardLimit) ? globalThis.Number(object.listingHardLimit) : undefined, }; }, @@ -1903,6 +2029,9 @@ export const RecordApiConfig: MessageFns = { if (message.expand?.length) { obj.expand = message.expand; } + if (message.listingHardLimit !== undefined && message.listingHardLimit !== 0) { + obj.listingHardLimit = Math.round(message.listingHardLimit); + } return obj; }, @@ -1925,6 +2054,7 @@ export const RecordApiConfig: MessageFns = { message.deleteAccessRule = object.deleteAccessRule ?? ""; message.schemaAccessRule = object.schemaAccessRule ?? ""; message.expand = object.expand?.map((e) => e) || []; + message.listingHardLimit = object.listingHardLimit ?? 0; return message; }, }; @@ -1946,7 +2076,7 @@ export const JsonSchemaConfig: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): JsonSchemaConfig { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseJsonSchemaConfig(); while (reader.pos < end) { const tag = reader.uint32(); @@ -2034,7 +2164,7 @@ export const Config: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): Config { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseConfig(); while (reader.pos < end) { const tag = reader.uint32(); diff --git a/crates/assets/js/admin/proto/config_api.ts b/crates/assets/js/admin/proto/config_api.ts index 0f24bc6f..d5d49349 100644 --- a/crates/assets/js/admin/proto/config_api.ts +++ b/crates/assets/js/admin/proto/config_api.ts @@ -1,6 +1,6 @@ // Code generated by protoc-gen-ts_proto. DO NOT EDIT. // versions: -// protoc-gen-ts_proto v2.7.0 +// protoc-gen-ts_proto v2.7.7 // protoc v3.21.12 // source: config_api.proto @@ -37,7 +37,7 @@ export const GetConfigResponse: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): GetConfigResponse { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseGetConfigResponse(); while (reader.pos < end) { const tag = reader.uint32(); @@ -115,7 +115,7 @@ export const UpdateConfigRequest: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): UpdateConfigRequest { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseUpdateConfigRequest(); while (reader.pos < end) { const tag = reader.uint32(); diff --git a/crates/assets/js/admin/proto/google/protobuf/descriptor.ts b/crates/assets/js/admin/proto/google/protobuf/descriptor.ts index 8eff0066..a4760915 100644 --- a/crates/assets/js/admin/proto/google/protobuf/descriptor.ts +++ b/crates/assets/js/admin/proto/google/protobuf/descriptor.ts @@ -1,6 +1,6 @@ // Code generated by protoc-gen-ts_proto. DO NOT EDIT. // versions: -// protoc-gen-ts_proto v2.7.0 +// protoc-gen-ts_proto v2.7.7 // protoc v3.21.12 // source: google/protobuf/descriptor.proto @@ -1251,7 +1251,7 @@ export const FileDescriptorSet: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): FileDescriptorSet { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseFileDescriptorSet(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1320,16 +1320,12 @@ export const FileDescriptorProto: MessageFns = { for (const v of message.dependency) { writer.uint32(26).string(v!); } - writer.uint32(82).fork(); for (const v of message.publicDependency) { - writer.int32(v); + writer.uint32(80).int32(v!); } - writer.join(); - writer.uint32(90).fork(); for (const v of message.weakDependency) { - writer.int32(v); + writer.uint32(88).int32(v!); } - writer.join(); for (const v of message.messageType) { DescriptorProto.encode(v!, writer.uint32(34).fork()).join(); } @@ -1356,7 +1352,7 @@ export const FileDescriptorProto: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): FileDescriptorProto { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseFileDescriptorProto(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1633,7 +1629,7 @@ export const DescriptorProto: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): DescriptorProto { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseDescriptorProto(); while (reader.pos < end) { const tag = reader.uint32(); @@ -1834,7 +1830,7 @@ export const DescriptorProto_ExtensionRange: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): ExtensionRangeOptions { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseExtensionRangeOptions(); while (reader.pos < end) { const tag = reader.uint32(); @@ -2094,7 +2090,7 @@ export const FieldDescriptorProto: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): FieldDescriptorProto { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseFieldDescriptorProto(); while (reader.pos < end) { const tag = reader.uint32(); @@ -2289,7 +2285,7 @@ export const OneofDescriptorProto: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): OneofDescriptorProto { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseOneofDescriptorProto(); while (reader.pos < end) { const tag = reader.uint32(); @@ -2376,7 +2372,7 @@ export const EnumDescriptorProto: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): EnumDescriptorProto { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseEnumDescriptorProto(); while (reader.pos < end) { const tag = reader.uint32(); @@ -2500,7 +2496,7 @@ export const EnumDescriptorProto_EnumReservedRange: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): EnumValueDescriptorProto { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseEnumValueDescriptorProto(); while (reader.pos < end) { const tag = reader.uint32(); @@ -2677,7 +2673,7 @@ export const ServiceDescriptorProto: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): ServiceDescriptorProto { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseServiceDescriptorProto(); while (reader.pos < end) { const tag = reader.uint32(); @@ -2782,7 +2778,7 @@ export const MethodDescriptorProto: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): MethodDescriptorProto { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseMethodDescriptorProto(); while (reader.pos < end) { const tag = reader.uint32(); @@ -2969,7 +2965,7 @@ export const FileOptions: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): FileOptions { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseFileOptions(); while (reader.pos < end) { const tag = reader.uint32(); @@ -3311,7 +3307,7 @@ export const MessageOptions: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): MessageOptions { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseMessageOptions(); while (reader.pos < end) { const tag = reader.uint32(); @@ -3450,7 +3446,7 @@ export const FieldOptions: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): FieldOptions { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseFieldOptions(); while (reader.pos < end) { const tag = reader.uint32(); @@ -3603,7 +3599,7 @@ export const OneofOptions: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): OneofOptions { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseOneofOptions(); while (reader.pos < end) { const tag = reader.uint32(); @@ -3671,7 +3667,7 @@ export const EnumOptions: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): EnumOptions { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseEnumOptions(); while (reader.pos < end) { const tag = reader.uint32(); @@ -3762,7 +3758,7 @@ export const EnumValueOptions: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): EnumValueOptions { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseEnumValueOptions(); while (reader.pos < end) { const tag = reader.uint32(); @@ -3840,7 +3836,7 @@ export const ServiceOptions: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): ServiceOptions { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseServiceOptions(); while (reader.pos < end) { const tag = reader.uint32(); @@ -3921,7 +3917,7 @@ export const MethodOptions: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): MethodOptions { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseMethodOptions(); while (reader.pos < end) { const tag = reader.uint32(); @@ -4029,7 +4025,7 @@ export const UninterpretedOption: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): UninterpretedOption { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseUninterpretedOption(); while (reader.pos < end) { const tag = reader.uint32(); @@ -4172,7 +4168,7 @@ export const UninterpretedOption_NamePart: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): SourceCodeInfo { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseSourceCodeInfo(); while (reader.pos < end) { const tag = reader.uint32(); @@ -4323,7 +4319,7 @@ export const SourceCodeInfo_Location: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): SourceCodeInfo_Location { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseSourceCodeInfo_Location(); while (reader.pos < end) { const tag = reader.uint32(); @@ -4457,7 +4453,7 @@ export const GeneratedCodeInfo: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): GeneratedCodeInfo { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseGeneratedCodeInfo(); while (reader.pos < end) { const tag = reader.uint32(); @@ -4530,7 +4526,7 @@ export const GeneratedCodeInfo_Annotation: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): Vault { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseVault(); while (reader.pos < end) { const tag = reader.uint32(); @@ -114,7 +114,7 @@ export const Vault_SecretsEntry: MessageFns = { decode(input: BinaryReader | Uint8Array, length?: number): Vault_SecretsEntry { const reader = input instanceof BinaryReader ? input : new BinaryReader(input); - let end = length === undefined ? reader.len : reader.pos + length; + const end = length === undefined ? reader.len : reader.pos + length; const message = createBaseVault_SecretsEntry(); while (reader.pos < end) { const tag = reader.uint32(); diff --git a/crates/core/proto/config.proto b/crates/core/proto/config.proto index d18698b9..5b966121 100644 --- a/crates/core/proto/config.proto +++ b/crates/core/proto/config.proto @@ -11,12 +11,22 @@ message EmailTemplate { optional string body = 2; } +enum SmtpEncryption { + SMTP_ENCRYPTION_UNDEFINED = 0; + SMTP_ENCRYPTION_NONE = 1; + SMTP_ENCRYPTION_STARTTLS = 2; + SMTP_ENCRYPTION_TLS = 3; +} + message EmailConfig { optional string smtp_host = 1; optional uint32 smtp_port = 2; optional string smtp_username = 3; optional string smtp_password = 4 [ (secret) = true ]; + // Which encryption method to use. STARTTLS by default. + optional SmtpEncryption smtp_encryption = 5; + optional string sender_name = 11; optional string sender_address = 12; diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs index efcdbe75..09756449 100644 --- a/crates/core/src/config.rs +++ b/crates/core/src/config.rs @@ -3,7 +3,7 @@ use log::*; use prost_reflect::{ DynamicMessage, ExtensionDescriptor, FieldDescriptor, Kind, MapKey, ReflectMessage, Value, }; -use proto::{EmailTemplate, OAuthProviderId}; +use proto::{EmailTemplate, OAuthProviderId, SmtpEncryption}; use std::collections::{HashMap, HashSet}; use std::convert::TryFrom; use std::str::FromStr; @@ -480,10 +480,6 @@ pub(crate) fn validate_config( tables: &SchemaMetadataCache, config: &proto::Config, ) -> Result<(), ConfigError> { - fn ierr(msg: impl Into) -> Result<(), ConfigError> { - return Err(ConfigError::Invalid(msg.into())); - } - // Check server settings. let Some(ref app_name) = config.server.application_name else { return ierr("Missing application name"); @@ -583,79 +579,7 @@ pub(crate) fn validate_config( } // Check email config. - { - let email = &config.email; - - let mut num_smtp_fields = 0; - if let Some(ref host) = email.smtp_host { - if !format!("http://{host}/").validate_url() { - return ierr(format!("Invalid SMTP host {host}.")); - } - num_smtp_fields += 1; - } - - if let Some(port) = email.smtp_port { - let port = u16::try_from(port).map_err(|_| ConfigError::Invalid("not a u16".into()))?; - if port == 0 { - return ierr("Invalid SMTP port."); - } - num_smtp_fields += 1; - } - - if let Some(ref username) = email.smtp_username { - if username.is_empty() { - return ierr("Invalid SMTP username."); - } - num_smtp_fields += 1; - } - - if let Some(ref password) = email.smtp_password { - if password.is_empty() { - return ierr("Invalid SMTP username."); - } - num_smtp_fields += 1; - } - - if num_smtp_fields != 0 && num_smtp_fields != 4 { - return ierr("Only a subset of SMTP settings provided"); - } - - if let Some(ref sender_address) = email.sender_address { - if !sender_address.validate_email() { - return ierr("Invalid sender address."); - }; - if email.sender_name.is_none() { - return ierr("Sender address but missing sender name."); - } - } - - fn validate_template(template: Option<&EmailTemplate>) -> Result<(), ConfigError> { - // NOTE: It's ok for either subject or body to be empty, we'll simply fall back to the - // defaults. - - // Check that VERIFICATION_URL is present. - if let Some(ref body) = template.as_ref().and_then(|t| t.body.as_ref()) { - lazy_static! { - static ref URL_PATTERN: regex::Regex = - regex::Regex::new(r#"\{\{[ ]*VERIFICATION_URL[ ]*\}\}"#).expect("static"); - static ref CODE_PATTERN: regex::Regex = - regex::Regex::new(r#"\{\{[ ]*CODE[ ]*\}\}"#).expect("static"); - }; - - if !(URL_PATTERN.is_match(body) || CODE_PATTERN.is_match(body)) { - return ierr(format!( - "Body needs to contain '{{{{ VERIFICATION_URL }}}}, got: {body}'" - )); - } - } - - return Ok(()); - } - - validate_template(email.user_verification_template.as_ref())?; - validate_template(email.change_email_template.as_ref())?; - validate_template(email.password_reset_template.as_ref())?; - } + validate_email_config(&config.email)?; // Check job config. for job in &config.jobs.system_jobs { @@ -675,6 +599,108 @@ pub(crate) fn validate_config( return Ok(()); } +pub(crate) fn validate_email_config(email: &proto::EmailConfig) -> Result<(), ConfigError> { + validate_email_template(email.user_verification_template.as_ref())?; + validate_email_template(email.change_email_template.as_ref())?; + validate_email_template(email.password_reset_template.as_ref())?; + + let Some(_host) = &email.smtp_host else { + match (email.smtp_port, &email.smtp_username, &email.smtp_password) { + (None, None, None) => { + // No SMTP configured + return Ok(()); + } + _ => { + return ierr("Partial SMTP configuration provided. Host missing."); + } + } + }; + + // TODO: check that `_host` is a valid hostname or IP. + + // NOTE: When no explicit sender is given, we fall back to noreply@host. + if let Some(ref sender_address) = email.sender_address { + if !sender_address.validate_email() { + return ierr("Invalid sender address."); + }; + if email.sender_name.is_none() { + return ierr("Sender address but missing sender name."); + } + } + + let _port: u16 = match email.smtp_port { + Some(port) => { + // NOTE: Protobuf doesn't support uint16 types natively, so we have to range-check. + let port = u16::try_from(port).map_err(|_| ConfigError::Invalid("not a u16".into()))?; + if port == 0 { + return ierr("Invalid SMTP port."); + } + port + } + None => { + return ierr("SMTP port missing."); + } + }; + + let user = &email.smtp_username; + let pw = &email.smtp_password; + + match email.smtp_encryption() { + SmtpEncryption::None => { + return match (user, pw) { + (None, None) => Ok(()), + _ => ierr("SMTP username or password provided, though encryption turned off."), + }; + } + _enc => { + if let Some(user) = user { + if user.is_empty() { + return ierr("Invalid SMTP username."); + } + } else { + return ierr("Missing SMTP username."); + } + + if let Some(pw) = pw { + if pw.is_empty() { + return ierr("Invalid SMTP username."); + } + } else { + return ierr("Missing SMTP password."); + } + } + } + + return Ok(()); +} + +fn validate_email_template(template: Option<&EmailTemplate>) -> Result<(), ConfigError> { + // NOTE: It's ok for either subject or body to be empty, we'll simply fall back to the + // defaults. + + // Check that VERIFICATION_URL is present. + if let Some(ref body) = template.as_ref().and_then(|t| t.body.as_ref()) { + lazy_static! { + static ref URL_PATTERN: regex::Regex = + regex::Regex::new(r#"\{\{[ ]*VERIFICATION_URL[ ]*\}\}"#).expect("static"); + static ref CODE_PATTERN: regex::Regex = + regex::Regex::new(r#"\{\{[ ]*CODE[ ]*\}\}"#).expect("static"); + }; + + if !(URL_PATTERN.is_match(body) || CODE_PATTERN.is_match(body)) { + return ierr(format!( + "Body needs to contain '{{{{ VERIFICATION_URL }}}}, got: {body}'" + )); + } + } + + return Ok(()); +} + +fn ierr(msg: impl std::string::ToString) -> Result<(), ConfigError> { + return Err(ConfigError::Invalid(msg.to_string())); +} + #[cfg(test)] mod test_env { use lazy_static::lazy_static; diff --git a/crates/core/src/email.rs b/crates/core/src/email.rs index 33d58c2e..af2eb366 100644 --- a/crates/core/src/email.rs +++ b/crates/core/src/email.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use thiserror::Error; use crate::AppState; -use crate::config::proto::Config; +use crate::config::proto::{Config, EmailConfig, SmtpEncryption}; use crate::constants::AUTH_API_PATH; #[derive(Debug, Error)] @@ -256,12 +256,34 @@ pub(crate) enum Mailer { } impl Mailer { - fn new_smtp(host: String, port: u16, user: String, pass: String) -> Result { - let mailer = AsyncSmtpTransport::::starttls_relay(&host)? - .port(port) - .credentials(smtp::authentication::Credentials::new(user, pass)) - .build(); - return Ok(Mailer::Smtp(Arc::new(mailer))); + fn new_smtp( + host: &str, + port: u16, + user: Option, + pass: Option, + encryption: SmtpEncryption, + ) -> Result { + let transport = match encryption { + SmtpEncryption::None => { + AsyncSmtpTransport::::builder_dangerous(host).port(port) + } + SmtpEncryption::Starttls | SmtpEncryption::Undefined => { + AsyncSmtpTransport::::starttls_relay(host)? + .port(port) + .credentials(smtp::authentication::Credentials::new( + user.ok_or(EmailError::Missing("SMTP username"))?, + pass.ok_or(EmailError::Missing("SMTP password"))?, + )) + } + SmtpEncryption::Tls => AsyncSmtpTransport::::relay(host)? + .port(port) + .credentials(smtp::authentication::Credentials::new( + user.ok_or(EmailError::Missing("SMTP username"))?, + pass.ok_or(EmailError::Missing("SMTP password"))?, + )), + }; + + return Ok(Mailer::Smtp(Arc::new(transport.build()))); } fn new_local() -> Mailer { @@ -269,33 +291,32 @@ impl Mailer { } pub(crate) fn new_from_config(config: &Config) -> Mailer { - let smtp_from_config = || -> Result { - let email = &config.email; + fn smtp_from_config(email: &EmailConfig) -> Result { let host = email .smtp_host - .to_owned() + .as_deref() .ok_or(EmailError::Missing("SMTP host"))?; let port = email .smtp_port - .map(|port| port as u16) + .and_then(|port| u16::try_from(port).ok()) .ok_or(EmailError::Missing("SMTP port"))?; - let user = email - .smtp_username - .to_owned() - .ok_or(EmailError::Missing("SMTP username"))?; - let pass = email - .smtp_password - .to_owned() - .ok_or(EmailError::Missing("SMTP password"))?; - Self::new_smtp(host, port, user, pass) - }; - - if let Ok(mailer) = smtp_from_config() { - return mailer; + return Mailer::new_smtp( + host, + port, + email.smtp_username.clone(), + email.smtp_password.clone(), + email.smtp_encryption(), + ); } - return Self::new_local(); + return match smtp_from_config(&config.email) { + Ok(mailer) => mailer, + Err(err) => { + info!("Falling back to local sendmail: {err}"); + Self::new_local() + } + }; } }