config.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. import os
  2. import random
  3. import socket
  4. import string
  5. import subprocess
  6. from urllib.parse import urlparse
  7. from dotenv import load_dotenv
  8. SHA1 = subprocess.getoutput("git rev-parse HEAD")
  9. ROOT_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
  10. def get_abs_path(file_path: str):
  11. """append ROOT_DIR for relative path"""
  12. # Already absolute path
  13. if file_path.startswith("/"):
  14. return file_path
  15. else:
  16. return os.path.join(ROOT_DIR, file_path)
  17. config_file = os.environ.get("CONFIG")
  18. if config_file:
  19. config_file = get_abs_path(config_file)
  20. print("load config file", config_file)
  21. load_dotenv(get_abs_path(config_file))
  22. else:
  23. load_dotenv()
  24. RESET_DB = "RESET_DB" in os.environ
  25. COLOR_LOG = "COLOR_LOG" in os.environ
  26. # Allow user to have 1 year of premium: set the expiration_date to 1 year more
  27. PROMO_CODE = "SIMPLEISBETTER"
  28. # Debug mode
  29. DEBUG = os.environ["DEBUG"] if "DEBUG" in os.environ else False
  30. # Server url
  31. URL = os.environ["URL"]
  32. print(">>> URL:", URL)
  33. # Calculate RP_ID for WebAuthn
  34. RP_ID = urlparse(URL).hostname
  35. SENTRY_DSN = os.environ.get("SENTRY_DSN")
  36. # can use another sentry project for the front-end to avoid noises
  37. SENTRY_FRONT_END_DSN = os.environ.get("SENTRY_FRONT_END_DSN") or SENTRY_DSN
  38. # Email related settings
  39. NOT_SEND_EMAIL = "NOT_SEND_EMAIL" in os.environ
  40. EMAIL_DOMAIN = os.environ["EMAIL_DOMAIN"].lower()
  41. SUPPORT_EMAIL = os.environ["SUPPORT_EMAIL"]
  42. SUPPORT_NAME = os.environ.get("SUPPORT_NAME", "Son from SimpleLogin")
  43. ADMIN_EMAIL = os.environ.get("ADMIN_EMAIL")
  44. try:
  45. MAX_NB_EMAIL_FREE_PLAN = int(os.environ["MAX_NB_EMAIL_FREE_PLAN"])
  46. except Exception:
  47. print("MAX_NB_EMAIL_FREE_PLAN is not set, use 5 as default value")
  48. MAX_NB_EMAIL_FREE_PLAN = 5
  49. # maximum number of directory a premium user can create
  50. MAX_NB_DIRECTORY = 50
  51. # transactional email sender
  52. SENDER = os.environ.get("SENDER")
  53. # the directory to store bounce emails
  54. SENDER_DIR = os.environ.get("SENDER_DIR")
  55. ENFORCE_SPF = "ENFORCE_SPF" in os.environ
  56. # allow to override postfix server locally
  57. POSTFIX_SERVER = os.environ.get("POSTFIX_SERVER", "240.0.0.1")
  58. DISABLE_REGISTRATION = "DISABLE_REGISTRATION" in os.environ
  59. # allow using a different postfix port, useful when developing locally
  60. POSTFIX_PORT = None
  61. if "POSTFIX_PORT" in os.environ:
  62. POSTFIX_PORT = int(os.environ["POSTFIX_PORT"])
  63. # Use port 587 instead of 25 when sending emails through Postfix
  64. # Useful when calling Postfix from an external network
  65. POSTFIX_SUBMISSION_TLS = "POSTFIX_SUBMISSION_TLS" in os.environ
  66. if "OTHER_ALIAS_DOMAINS" in os.environ:
  67. OTHER_ALIAS_DOMAINS = eval(
  68. os.environ["OTHER_ALIAS_DOMAINS"]
  69. ) # ["domain1.com", "domain2.com"]
  70. else:
  71. OTHER_ALIAS_DOMAINS = []
  72. # List of domains user can use to create alias
  73. if "ALIAS_DOMAINS" in os.environ:
  74. ALIAS_DOMAINS = eval(os.environ["ALIAS_DOMAINS"]) # ["domain1.com", "domain2.com"]
  75. else:
  76. ALIAS_DOMAINS = OTHER_ALIAS_DOMAINS + [EMAIL_DOMAIN]
  77. ALIAS_DOMAINS = [d.lower().strip() for d in ALIAS_DOMAINS]
  78. if "PREMIUM_ALIAS_DOMAINS" in os.environ:
  79. PREMIUM_ALIAS_DOMAINS = eval(
  80. os.environ["PREMIUM_ALIAS_DOMAINS"]
  81. ) # ["domain1.com", "domain2.com"]
  82. else:
  83. PREMIUM_ALIAS_DOMAINS = []
  84. PREMIUM_ALIAS_DOMAINS = [d.lower().strip() for d in PREMIUM_ALIAS_DOMAINS]
  85. # the alias domain used when creating the first alias for user
  86. FIRST_ALIAS_DOMAIN = os.environ.get("FIRST_ALIAS_DOMAIN") or EMAIL_DOMAIN
  87. # list of (priority, email server)
  88. EMAIL_SERVERS_WITH_PRIORITY = eval(
  89. os.environ["EMAIL_SERVERS_WITH_PRIORITY"]
  90. ) # [(10, "email.hostname.")]
  91. # these emails are ignored when computing stats
  92. if os.environ.get("IGNORED_EMAILS"):
  93. IGNORED_EMAILS = eval(os.environ.get("IGNORED_EMAILS"))
  94. else:
  95. IGNORED_EMAILS = []
  96. # disable the alias suffix, i.e. the ".random_word" part
  97. DISABLE_ALIAS_SUFFIX = "DISABLE_ALIAS_SUFFIX" in os.environ
  98. # the email address that receives all unsubscription request
  99. UNSUBSCRIBER = os.environ.get("UNSUBSCRIBER")
  100. DKIM_PRIVATE_KEY_PATH = get_abs_path(os.environ["DKIM_PRIVATE_KEY_PATH"])
  101. DKIM_PUBLIC_KEY_PATH = get_abs_path(os.environ["DKIM_PUBLIC_KEY_PATH"])
  102. DKIM_SELECTOR = b"dkim"
  103. with open(DKIM_PRIVATE_KEY_PATH) as f:
  104. DKIM_PRIVATE_KEY = f.read()
  105. with open(DKIM_PUBLIC_KEY_PATH) as f:
  106. DKIM_DNS_VALUE = (
  107. f.read()
  108. .replace("-----BEGIN PUBLIC KEY-----", "")
  109. .replace("-----END PUBLIC KEY-----", "")
  110. .replace("\r", "")
  111. .replace("\n", "")
  112. )
  113. DKIM_HEADERS = [b"from", b"to"]
  114. # Database
  115. DB_URI = os.environ["DB_URI"]
  116. # Flask secret
  117. FLASK_SECRET = os.environ["FLASK_SECRET"]
  118. SESSION_COOKIE_NAME = "slapp"
  119. MAILBOX_SECRET = FLASK_SECRET + "mailbox"
  120. CUSTOM_ALIAS_SECRET = FLASK_SECRET + "custom_alias"
  121. # AWS
  122. AWS_REGION = "eu-west-3"
  123. BUCKET = os.environ.get("BUCKET")
  124. AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID")
  125. AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY")
  126. CLOUDWATCH_LOG_GROUP = CLOUDWATCH_LOG_STREAM = ""
  127. ENABLE_CLOUDWATCH = "ENABLE_CLOUDWATCH" in os.environ
  128. if ENABLE_CLOUDWATCH:
  129. CLOUDWATCH_LOG_GROUP = os.environ["CLOUDWATCH_LOG_GROUP"]
  130. CLOUDWATCH_LOG_STREAM = os.environ["CLOUDWATCH_LOG_STREAM"]
  131. # Paddle
  132. try:
  133. PADDLE_VENDOR_ID = int(os.environ["PADDLE_VENDOR_ID"])
  134. PADDLE_MONTHLY_PRODUCT_ID = int(os.environ["PADDLE_MONTHLY_PRODUCT_ID"])
  135. PADDLE_YEARLY_PRODUCT_ID = int(os.environ["PADDLE_YEARLY_PRODUCT_ID"])
  136. except:
  137. print("Paddle param not set")
  138. PADDLE_VENDOR_ID = -1
  139. PADDLE_MONTHLY_PRODUCT_ID = -1
  140. PADDLE_YEARLY_PRODUCT_ID = -1
  141. PADDLE_PUBLIC_KEY_PATH = get_abs_path(
  142. os.environ.get("PADDLE_PUBLIC_KEY_PATH", "local_data/paddle.key.pub")
  143. )
  144. PADDLE_AUTH_CODE = os.environ.get("PADDLE_AUTH_CODE")
  145. # OpenID keys, used to sign id_token
  146. OPENID_PRIVATE_KEY_PATH = get_abs_path(
  147. os.environ.get("OPENID_PRIVATE_KEY_PATH", "local_data/jwtRS256.key")
  148. )
  149. OPENID_PUBLIC_KEY_PATH = get_abs_path(
  150. os.environ.get("OPENID_PUBLIC_KEY_PATH", "local_data/jwtRS256.key.pub")
  151. )
  152. # Used to generate random email
  153. WORDS_FILE_PATH = get_abs_path(
  154. os.environ.get("WORDS_FILE_PATH", "local_data/words_alpha.txt")
  155. )
  156. # Used to generate random email
  157. if os.environ.get("GNUPGHOME"):
  158. GNUPGHOME = get_abs_path(os.environ.get("GNUPGHOME"))
  159. else:
  160. letters = string.ascii_lowercase
  161. random_dir_name = "".join(random.choice(letters) for _ in range(20))
  162. GNUPGHOME = f"/tmp/{random_dir_name}"
  163. if not os.path.exists(GNUPGHOME):
  164. os.mkdir(GNUPGHOME, mode=0o700)
  165. print("WARNING: Use a temp directory for GNUPGHOME", GNUPGHOME)
  166. # Github, Google, Facebook client id and secrets
  167. GITHUB_CLIENT_ID = os.environ.get("GITHUB_CLIENT_ID")
  168. GITHUB_CLIENT_SECRET = os.environ.get("GITHUB_CLIENT_SECRET")
  169. GOOGLE_CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID")
  170. GOOGLE_CLIENT_SECRET = os.environ.get("GOOGLE_CLIENT_SECRET")
  171. FACEBOOK_CLIENT_ID = os.environ.get("FACEBOOK_CLIENT_ID")
  172. FACEBOOK_CLIENT_SECRET = os.environ.get("FACEBOOK_CLIENT_SECRET")
  173. # in seconds
  174. AVATAR_URL_EXPIRATION = 3600 * 24 * 7 # 1h*24h/d*7d=1week
  175. # session key
  176. MFA_USER_ID = "mfa_user_id"
  177. FLASK_PROFILER_PATH = os.environ.get("FLASK_PROFILER_PATH")
  178. FLASK_PROFILER_PASSWORD = os.environ.get("FLASK_PROFILER_PASSWORD")
  179. # Job names
  180. JOB_ONBOARDING_1 = "onboarding-1"
  181. JOB_ONBOARDING_2 = "onboarding-2"
  182. JOB_ONBOARDING_3 = "onboarding-3"
  183. JOB_ONBOARDING_4 = "onboarding-4"
  184. JOB_BATCH_IMPORT = "batch-import"
  185. # for pagination
  186. PAGE_LIMIT = 20
  187. # Upload to static/upload instead of s3
  188. LOCAL_FILE_UPLOAD = "LOCAL_FILE_UPLOAD" in os.environ
  189. UPLOAD_DIR = None
  190. # Greylisting features
  191. # nb max of activity (forward/reply) an alias can have during 1 min
  192. MAX_ACTIVITY_DURING_MINUTE_PER_ALIAS = 5
  193. # nb max of activity (forward/reply) a mailbox can have during 1 min
  194. MAX_ACTIVITY_DURING_MINUTE_PER_MAILBOX = 10
  195. if LOCAL_FILE_UPLOAD:
  196. print("Upload files to local dir")
  197. UPLOAD_DIR = os.path.join(ROOT_DIR, "static/upload")
  198. if not os.path.exists(UPLOAD_DIR):
  199. print("Create upload dir")
  200. os.makedirs(UPLOAD_DIR)
  201. LANDING_PAGE_URL = os.environ.get("LANDING_PAGE_URL") or "https://simplelogin.io"
  202. STATUS_PAGE_URL = os.environ.get("STATUS_PAGE_URL") or "https://status.simplelogin.io"
  203. # Loading PGP keys when mail_handler runs. To be used locally when init_app is not called.
  204. LOAD_PGP_EMAIL_HANDLER = "LOAD_PGP_EMAIL_HANDLER" in os.environ
  205. DISPOSABLE_FILE_PATH = get_abs_path(
  206. os.environ.get("DISPOSABLE_FILE_PATH", "local_data/local_disposable_domains.txt")
  207. )
  208. with open(get_abs_path(DISPOSABLE_FILE_PATH), "r") as f:
  209. DISPOSABLE_EMAIL_DOMAINS = f.readlines()
  210. DISPOSABLE_EMAIL_DOMAINS = [d.strip().lower() for d in DISPOSABLE_EMAIL_DOMAINS]
  211. DISPOSABLE_EMAIL_DOMAINS = [
  212. d for d in DISPOSABLE_EMAIL_DOMAINS if not d.startswith("#")
  213. ]
  214. # Used when querying info on Apple API
  215. # for iOS App
  216. APPLE_API_SECRET = os.environ.get("APPLE_API_SECRET")
  217. # for Mac App
  218. MACAPP_APPLE_API_SECRET = os.environ.get("MACAPP_APPLE_API_SECRET")
  219. # <<<<< ALERT EMAIL >>>>
  220. # maximal number of alerts that can be sent to the same email in 24h
  221. MAX_ALERT_24H = 4
  222. # When a reverse-alias receives emails from un unknown mailbox
  223. ALERT_REVERSE_ALIAS_UNKNOWN_MAILBOX = "reverse_alias_unknown_mailbox"
  224. # When a forwarding email is bounced
  225. ALERT_BOUNCE_EMAIL = "bounce"
  226. ALERT_BOUNCE_EMAIL_REPLY_PHASE = "bounce-when-reply"
  227. # When a forwarding email is detected as spam
  228. ALERT_SPAM_EMAIL = "spam"
  229. # When an email is sent from a mailbox to an alias - a cycle
  230. ALERT_SEND_EMAIL_CYCLE = "cycle"
  231. ALERT_SPF = "spf"
  232. # when a mailbox is also an alias
  233. # happens when user adds a mailbox with their domain
  234. # then later adds this domain into SimpleLogin
  235. ALERT_MAILBOX_IS_ALIAS = "mailbox_is_alias"
  236. AlERT_WRONG_MX_RECORD_CUSTOM_DOMAIN = "custom_domain_mx_record_issue"
  237. # <<<<< END ALERT EMAIL >>>>
  238. # Disable onboarding emails
  239. DISABLE_ONBOARDING = "DISABLE_ONBOARDING" in os.environ
  240. HCAPTCHA_SECRET = os.environ.get("HCAPTCHA_SECRET")
  241. HCAPTCHA_SITEKEY = os.environ.get("HCAPTCHA_SITEKEY")
  242. PLAUSIBLE_HOST = os.environ.get("PLAUSIBLE_HOST")
  243. PLAUSIBLE_DOMAIN = os.environ.get("PLAUSIBLE_DOMAIN")
  244. # server host
  245. HOST = socket.gethostname()
  246. # by default use a tolerant score
  247. MAX_SPAM_SCORE = 5.5
  248. SPAMASSASSIN_HOST = os.environ.get("SPAMASSASSIN_HOST")
  249. # use a more restrictive score when replying
  250. MAX_REPLY_PHASE_SPAM_SCORE = 5
  251. PGP_SENDER_PRIVATE_KEY = None
  252. PGP_SENDER_PRIVATE_KEY_PATH = os.environ.get("PGP_SENDER_PRIVATE_KEY_PATH")
  253. if PGP_SENDER_PRIVATE_KEY_PATH:
  254. with open(get_abs_path(PGP_SENDER_PRIVATE_KEY_PATH)) as f:
  255. PGP_SENDER_PRIVATE_KEY = f.read()