Allow configuring SMTP encryption type: None, TLS, STARTTLS. #122

This commit is contained in:
Sebastian Jeltsch
2025-09-11 12:00:55 +02:00
parent 99a9effe36
commit 3aab2be43a
7 changed files with 350 additions and 167 deletions

View File

@@ -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<EmailTemplate> = {
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<EmailConfig> = {
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<EmailConfig> = {
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<EmailConfig> = {
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<EmailConfig> = {
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<EmailConfig> = {
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<EmailConfig> = {
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<OAuthProviderConfig> = {
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<OAuthProviderConfig> = {
};
function createBaseAuthConfig(): AuthConfig {
return { oauthProviders: {} };
return { oauthProviders: {}, customUriSchemes: [] };
}
export const AuthConfig: MessageFns<AuthConfig> = {
@@ -977,12 +1059,15 @@ export const AuthConfig: MessageFns<AuthConfig> = {
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<AuthConfig> = {
}
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<AuthConfig> = {
return acc;
}, {})
: {},
customUriSchemes: globalThis.Array.isArray(object?.customUriSchemes)
? object.customUriSchemes.map((e: any) => globalThis.String(e))
: [],
};
},
@@ -1129,6 +1225,9 @@ export const AuthConfig: MessageFns<AuthConfig> = {
});
}
}
if (message.customUriSchemes?.length) {
obj.customUriSchemes = message.customUriSchemes;
}
return obj;
},
@@ -1153,6 +1252,7 @@ export const AuthConfig: MessageFns<AuthConfig> = {
},
{},
);
message.customUriSchemes = object.customUriSchemes?.map((e) => e) || [];
return message;
},
};
@@ -1174,7 +1274,7 @@ export const AuthConfig_OauthProvidersEntry: MessageFns<AuthConfig_OauthProvider
decode(input: BinaryReader | Uint8Array, length?: number): AuthConfig_OauthProvidersEntry {
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_OauthProvidersEntry();
while (reader.pos < end) {
const tag = reader.uint32();
@@ -1263,7 +1363,7 @@ export const S3StorageConfig: MessageFns<S3StorageConfig> = {
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<ServerConfig> = {
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<ServerConfig> = {
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<ServerConfig> = {
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<ServerConfig> = {
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<ServerConfig> = {
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<SystemJob> = {
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<JobsConfig> = {
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<RecordApiConfig> = {
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<RecordApiConfig> = {
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<RecordApiConfig> = {
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<RecordApiConfig> = {
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<RecordApiConfig> = {
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<RecordApiConfig> = {
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<JsonSchemaConfig> = {
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<Config> = {
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();

View File

@@ -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<GetConfigResponse> = {
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<UpdateConfigRequest> = {
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();

View File

@@ -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<FileDescriptorSet> = {
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<FileDescriptorProto> = {
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<FileDescriptorProto> = {
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<DescriptorProto> = {
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<DescriptorProto_Extensio
decode(input: BinaryReader | Uint8Array, length?: number): DescriptorProto_ExtensionRange {
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_ExtensionRange();
while (reader.pos < end) {
const tag = reader.uint32();
@@ -1927,7 +1923,7 @@ export const DescriptorProto_ReservedRange: MessageFns<DescriptorProto_ReservedR
decode(input: BinaryReader | Uint8Array, length?: number): DescriptorProto_ReservedRange {
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_ReservedRange();
while (reader.pos < end) {
const tag = reader.uint32();
@@ -2002,7 +1998,7 @@ export const ExtensionRangeOptions: MessageFns<ExtensionRangeOptions> = {
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<FieldDescriptorProto> = {
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<OneofDescriptorProto> = {
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<EnumDescriptorProto> = {
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<EnumDescriptorPro
decode(input: BinaryReader | Uint8Array, length?: number): EnumDescriptorProto_EnumReservedRange {
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_EnumReservedRange();
while (reader.pos < end) {
const tag = reader.uint32();
@@ -2583,7 +2579,7 @@ export const EnumValueDescriptorProto: MessageFns<EnumValueDescriptorProto> = {
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<ServiceDescriptorProto> = {
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<MethodDescriptorProto> = {
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<FileOptions> = {
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<MessageOptions> = {
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<FieldOptions> = {
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<OneofOptions> = {
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<EnumOptions> = {
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<EnumValueOptions> = {
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<ServiceOptions> = {
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<MethodOptions> = {
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<UninterpretedOption> = {
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<UninterpretedOption_NamePa
decode(input: BinaryReader | Uint8Array, length?: number): UninterpretedOption_NamePart {
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_NamePart();
while (reader.pos < end) {
const tag = reader.uint32();
@@ -4245,7 +4241,7 @@ export const SourceCodeInfo: MessageFns<SourceCodeInfo> = {
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<SourceCodeInfo_Location> = {
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<GeneratedCodeInfo> = {
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<GeneratedCodeInfo_Annotati
decode(input: BinaryReader | Uint8Array, length?: number): GeneratedCodeInfo_Annotation {
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_Annotation();
while (reader.pos < end) {
const tag = reader.uint32();

View File

@@ -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: vault.proto
@@ -32,7 +32,7 @@ export const Vault: MessageFns<Vault> = {
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<Vault_SecretsEntry> = {
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();

View File

@@ -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;

View File

@@ -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<String>) -> 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;

View File

@@ -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<Mailer, EmailError> {
let mailer = AsyncSmtpTransport::<Tokio1Executor>::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<String>,
pass: Option<String>,
encryption: SmtpEncryption,
) -> Result<Mailer, EmailError> {
let transport = match encryption {
SmtpEncryption::None => {
AsyncSmtpTransport::<Tokio1Executor>::builder_dangerous(host).port(port)
}
SmtpEncryption::Starttls | SmtpEncryption::Undefined => {
AsyncSmtpTransport::<Tokio1Executor>::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::<Tokio1Executor>::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<Mailer, EmailError> {
let email = &config.email;
fn smtp_from_config(email: &EmailConfig) -> Result<Mailer, EmailError> {
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()
}
};
}
}