Coverage for src/couchers/config.py: 52%

46 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-07-02 02:47 +0000

1""" 

2A simple config system 

3""" 

4 

5import os 

6 

7# Allowed config options, as tuples (name, type, default). 

8# All fields are required 

9CONFIG_OPTIONS = [ 

10 # Whether we're in dev mode 

11 ("DEV", bool), 

12 # Whether we're `api` mode (answering API queries) or `scheduler` (scheduling background jobs), or `worker` 

13 # (servicing background jobs). Can also be set to `all` to do all three simultaneously 

14 ("ROLE", ["api", "scheduler", "worker", "all"], "all"), 

15 # number of bg worker processes, requires worker or all above 

16 ("BACKGROUND_WORKER_COUNT", int, 2), 

17 # Version string 

18 ("VERSION", str, "unknown"), 

19 # Base URL of frontend, e.g. https://couchers.org 

20 ("BASE_URL", str), 

21 # URL of the backend, e.g. https://api.couchers.org 

22 ("BACKEND_BASE_URL", str), 

23 # URL of the console, e.g. https://console.couchers.org 

24 ("CONSOLE_BASE_URL", str), 

25 # Used to generate a variety of secrets 

26 ("SECRET", bytes), 

27 # Domain that cookies should set as their domain value 

28 ("COOKIE_DOMAIN", str), 

29 # SQLAlchemy database connection string 

30 ("DATABASE_CONNECTION_STRING", str), 

31 # OpenTelemetry endpoint to send traces to 

32 ("OPENTELEMETRY_ENDPOINT", str, ""), 

33 # Path to a GeoLite2-City.mmdb file for geocoding IPs in user session info 

34 ("GEOLITE2_CITY_MMDB_FILE_LOCATION", str, ""), 

35 ("GEOLITE2_ASN_MMDB_FILE_LOCATION", str, ""), 

36 # Whether to try adding dummy data 

37 ("ADD_DUMMY_DATA", bool), 

38 # Donations 

39 ("ENABLE_DONATIONS", bool), 

40 ("STRIPE_API_KEY", str), 

41 ("STRIPE_WEBHOOK_SECRET", str), 

42 ("STRIPE_RECURRING_PRODUCT_ID", str), 

43 # Strong verification through Iris ID 

44 ("ENABLE_STRONG_VERIFICATION", bool), 

45 ("IRIS_ID_PUBKEY", str), 

46 ("IRIS_ID_SECRET", str), 

47 ("VERIFICATION_DATA_PUBLIC_KEY", bytes), 

48 # SMS 

49 ("ENABLE_SMS", bool), 

50 ("SMS_SENDER_ID", str), 

51 # Email 

52 ("ENABLE_EMAIL", bool), 

53 # Sender name for outgoing notification emails e.g. "Couchers.org" 

54 ("NOTIFICATION_EMAIL_SENDER", str), 

55 # Sender email, e.g. "notify@couchers.org" 

56 ("NOTIFICATION_EMAIL_ADDRESS", str), 

57 # An optional prefix for email subject, e.g. [STAGING] 

58 ("NOTIFICATION_PREFIX", str, ""), 

59 # Address to send emails about reported users 

60 ("REPORTS_EMAIL_RECIPIENT", str), 

61 # Address to send contributor forms when users sign up/fill the form 

62 ("CONTRIBUTOR_FORM_EMAIL_RECIPIENT", str), 

63 # Address to moderation notifications 

64 ("MODS_EMAIL_RECIPIENT", str), 

65 # SMTP settings 

66 ("SMTP_HOST", str), 

67 ("SMTP_PORT", int), 

68 ("SMTP_USERNAME", str), 

69 ("SMTP_PASSWORD", str), 

70 # Media server 

71 ("ENABLE_MEDIA", bool), 

72 ("MEDIA_SERVER_SECRET_KEY", bytes), 

73 ("MEDIA_SERVER_BEARER_TOKEN", str), 

74 ("MEDIA_SERVER_BASE_URL", str), 

75 ("MEDIA_SERVER_UPLOAD_BASE_URL", str), 

76 # Bug reporting tool 

77 ("BUG_TOOL_ENABLED", bool), 

78 ("BUG_TOOL_GITHUB_REPO", str), 

79 ("BUG_TOOL_GITHUB_USERNAME", str), 

80 ("BUG_TOOL_GITHUB_TOKEN", str), 

81 # Sentry 

82 ("SENTRY_ENABLED", bool), 

83 ("SENTRY_URL", str), 

84 # Push notifications 

85 ("PUSH_NOTIFICATIONS_ENABLED", bool), 

86 ("PUSH_NOTIFICATIONS_VAPID_PRIVATE_KEY", str), 

87 ("PUSH_NOTIFICATIONS_VAPID_SUBJECT", str), 

88 # Whether to initiate new activeness probes 

89 ("ACTIVENESS_PROBES_ENABLED", bool), 

90 # Listmonk (mailing list) 

91 ("LISTMONK_ENABLED", bool), 

92 ("LISTMONK_BASE_URL", str), 

93 ("LISTMONK_API_USERNAME", str), 

94 ("LISTMONK_API_KEY", str), 

95 ("LISTMONK_LIST_ID", int), 

96 # Google recaptcha antibot 

97 ("RECAPTHCA_ENABLED", bool), 

98 ("RECAPTHCA_PROJECT_ID", str), 

99 ("RECAPTHCA_API_KEY", str), 

100 ("RECAPTHCA_SITE_KEY", str), 

101 # Whether we're in test 

102 ("IN_TEST", bool, "0"), 

103] 

104 

105config = {} 

106 

107for config_option in CONFIG_OPTIONS: 

108 if len(config_option) == 2: 

109 name, type_ = config_option 

110 optional = False 

111 elif len(config_option) == 3: 

112 name, type_, default_value = config_option 

113 optional = True 

114 else: 

115 raise ValueError("Invalid CONFIG_OPTIONS") 

116 

117 value = os.getenv(name) 

118 

119 if not value: 

120 if not optional: 

121 # config value not set - will cause a KeyError when trying 

122 # to access it. 

123 continue 

124 else: 

125 value = default_value 

126 

127 if type_ is bool: 

128 # 1 is true, 0 is false, everything else is illegal 

129 if value not in ["0", "1"]: 

130 raise ValueError(f'Invalid bool for {name}, need "0" or "1"') 

131 value = value == "1" 

132 elif type_ is bytes: 

133 # decode from hex 

134 value = bytes.fromhex(value) 

135 elif isinstance(type_, list): 

136 # list of allowed string values 

137 if value not in type_: 

138 raise ValueError(f"Invalid value for {name}, need one of {', '.join(type_)}") 

139 else: 

140 value = type_(value) 

141 

142 config[name] = value 

143 

144 

145## Config checks 

146def check_config(): 

147 for name, *_ in CONFIG_OPTIONS: 

148 if name not in config: 

149 raise ValueError(f"Required config value {name} not set") 

150 

151 if not config["DEV"]: 

152 # checks for prod 

153 if "https" not in config["BASE_URL"]: 

154 raise Exception("Production site must be over HTTPS") 

155 if not config["ENABLE_EMAIL"]: 

156 raise Exception("Production site must have email enabled") 

157 if not config["ENABLE_SMS"]: 

158 raise Exception("Production site must have SMS enabled") 

159 if config["IN_TEST"]: 

160 raise Exception("IN_TEST while not DEV") 

161 

162 if config["ENABLE_DONATIONS"]: 

163 if ( 

164 not config["STRIPE_API_KEY"] 

165 or not config["STRIPE_WEBHOOK_SECRET"] 

166 or not config["STRIPE_RECURRING_PRODUCT_ID"] 

167 ): 

168 raise Exception("No Stripe API key/recurring donation ID but donations enabled") 

169 

170 if config["ENABLE_STRONG_VERIFICATION"]: 

171 if not config["IRIS_ID_PUBKEY"] or not config["IRIS_ID_SECRET"] or not config["VERIFICATION_DATA_PUBLIC_KEY"]: 

172 raise Exception("No Iris ID pubkey/secret or verification data pubkey but strong verification enabled")