factory.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. package main
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "log/slog"
  7. "os"
  8. "strings"
  9. "time"
  10. "github.com/kelseyhightower/envconfig"
  11. "golang.org/x/time/rate"
  12. "github.com/mk6i/retro-aim-server/config"
  13. "github.com/mk6i/retro-aim-server/foodgroup"
  14. "github.com/mk6i/retro-aim-server/server/http"
  15. "github.com/mk6i/retro-aim-server/server/kerberos"
  16. "github.com/mk6i/retro-aim-server/server/oscar"
  17. "github.com/mk6i/retro-aim-server/server/oscar/middleware"
  18. "github.com/mk6i/retro-aim-server/server/toc"
  19. "github.com/mk6i/retro-aim-server/state"
  20. "github.com/mk6i/retro-aim-server/wire"
  21. )
  22. // Container groups together common dependencies.
  23. type Container struct {
  24. cfg config.Config
  25. chatSessionManager *state.InMemoryChatSessionManager
  26. hmacCookieBaker state.HMACCookieBaker
  27. inMemorySessionManager *state.InMemorySessionManager
  28. logger *slog.Logger
  29. rateLimitClasses wire.RateLimitClasses
  30. snacRateLimits wire.SNACRateLimits
  31. sqLiteUserStore *state.SQLiteUserStore
  32. Listeners []config.Listener
  33. }
  34. // MakeCommonDeps creates common dependencies used by the food group services.
  35. func MakeCommonDeps() (Container, error) {
  36. c := Container{}
  37. if err := validateConfigMigration(); err != nil {
  38. return c, fmt.Errorf("unable to validate config migration: %s\n", err.Error())
  39. }
  40. err := envconfig.Process("", &c.cfg)
  41. if err != nil {
  42. return c, fmt.Errorf("unable to process app config: %s\n", err.Error())
  43. }
  44. c.Listeners, err = config.ParseListenersCfg(c.cfg.BOSListeners, c.cfg.BOSAdvertisedHosts, c.cfg.KerberosListeners)
  45. if err != nil {
  46. return c, fmt.Errorf("unable to parse listener config: %s\n", err.Error())
  47. }
  48. c.sqLiteUserStore, err = state.NewSQLiteUserStore(c.cfg.DBPath)
  49. if err != nil {
  50. return c, fmt.Errorf("unable to create feedbag store: %s\n", err.Error())
  51. }
  52. c.hmacCookieBaker, err = state.NewHMACCookieBaker()
  53. if err != nil {
  54. return c, fmt.Errorf("unable to create HMAC cookie baker: %s\n", err.Error())
  55. }
  56. c.logger = middleware.NewLogger(c.cfg)
  57. c.inMemorySessionManager = state.NewInMemorySessionManager(c.logger)
  58. c.chatSessionManager = state.NewInMemoryChatSessionManager(c.logger)
  59. c.rateLimitClasses = wire.DefaultRateLimitClasses()
  60. c.snacRateLimits = wire.DefaultSNACRateLimits()
  61. return c, nil
  62. }
  63. func validateConfigMigration() error {
  64. // Old environment variables that should be removed
  65. oldEnvVars := []string{
  66. "API_HOST",
  67. "API_PORT",
  68. "KERBEROS_PORT",
  69. "ALERT_PORT",
  70. "AUTH_PORT",
  71. "BART_PORT",
  72. "BOS_PORT",
  73. "CHAT_NAV_PORT",
  74. "CHAT_PORT",
  75. "ADMIN_PORT",
  76. "ODIR_PORT",
  77. "OSCAR_HOST",
  78. "TOC_HOST",
  79. "TOC_PORT",
  80. }
  81. // New environment variables that should be present
  82. newEnvVars := []string{
  83. "API_LISTENER",
  84. "OSCAR_ADVERTISED_LISTENERS",
  85. "OSCAR_LISTENERS",
  86. "TOC_LISTENERS",
  87. }
  88. var oldEnvVarsFound []string
  89. var newEnvVarsMissing []string
  90. // Check for old environment variables that should be removed
  91. for _, envVar := range oldEnvVars {
  92. if os.Getenv(envVar) != "" {
  93. oldEnvVarsFound = append(oldEnvVarsFound, envVar)
  94. }
  95. }
  96. // Check for new environment variables that should be present
  97. for _, envVar := range newEnvVars {
  98. if os.Getenv(envVar) == "" {
  99. newEnvVarsMissing = append(newEnvVarsMissing, envVar)
  100. }
  101. }
  102. // If there are any issues, return an error with details
  103. if len(oldEnvVarsFound) > 0 || len(newEnvVarsMissing) > 0 {
  104. var errorMsg strings.Builder
  105. errorMsg.WriteString("Retro AIM Server v0.19.0 introduced some breaking configuration changes that you need to fix.\n")
  106. if len(oldEnvVarsFound) > 0 {
  107. errorMsg.WriteString("\nOld environment variables that must be removed:\n\n")
  108. for _, envVar := range oldEnvVarsFound {
  109. errorMsg.WriteString(fmt.Sprintf(" - %s\n", envVar))
  110. }
  111. }
  112. if len(newEnvVarsMissing) > 0 {
  113. errorMsg.WriteString("\nNew environment variables that must be provided:\n\n")
  114. for _, envVar := range newEnvVarsMissing {
  115. errorMsg.WriteString(fmt.Sprintf(" - %s\n", envVar))
  116. }
  117. // Generate export commands based on old environment variables
  118. errorMsg.WriteString("\nCopy/paste this updated configuration into your settings file:\n\n")
  119. if contains(newEnvVarsMissing, "API_LISTENER") {
  120. apiHost := getEnvOrDefault("API_HOST", "127.0.0.1")
  121. apiPort := getEnvOrDefault("API_PORT", "8080")
  122. errorMsg.WriteString(fmt.Sprintf("export API_LISTENER=%s:%s\n", apiHost, apiPort))
  123. }
  124. if contains(newEnvVarsMissing, "OSCAR_ADVERTISED_LISTENERS") {
  125. oscarHost := getEnvOrDefault("OSCAR_HOST", "127.0.0.1")
  126. authPort := getEnvOrDefault("AUTH_PORT", "5190")
  127. errorMsg.WriteString(fmt.Sprintf("export OSCAR_ADVERTISED_LISTENERS=EXTERNAL://%s:%s\n", oscarHost, authPort))
  128. }
  129. if contains(newEnvVarsMissing, "OSCAR_LISTENERS") {
  130. authPort := getEnvOrDefault("AUTH_PORT", "5190")
  131. errorMsg.WriteString(fmt.Sprintf("export OSCAR_LISTENERS=EXTERNAL://0.0.0.0:%s\n", authPort))
  132. }
  133. if contains(newEnvVarsMissing, "KERBEROS_LISTENERS") {
  134. kerberosPort := getEnvOrDefault("KERBEROS_PORT", "1088")
  135. errorMsg.WriteString(fmt.Sprintf("export KERBEROS_LISTENERS=EXTERNAL://0.0.0.0:%s\n", kerberosPort))
  136. }
  137. if contains(newEnvVarsMissing, "TOC_LISTENERS") {
  138. tocHost := getEnvOrDefault("TOC_HOST", "0.0.0.0")
  139. tocPort := getEnvOrDefault("TOC_PORT", "9898")
  140. errorMsg.WriteString(fmt.Sprintf("export TOC_LISTENERS=%s:%s\n", tocHost, tocPort))
  141. }
  142. }
  143. return errors.New(errorMsg.String())
  144. }
  145. return nil
  146. }
  147. // Helper function to check if a slice contains a string
  148. func contains(slice []string, item string) bool {
  149. for _, s := range slice {
  150. if s == item {
  151. return true
  152. }
  153. }
  154. return false
  155. }
  156. // Helper function to get environment variable or return default
  157. func getEnvOrDefault(key, defaultValue string) string {
  158. if value := os.Getenv(key); value != "" {
  159. return value
  160. }
  161. return defaultValue
  162. }
  163. // OSCAR creates an OSCAR server for the OSCAR food group.
  164. func OSCAR(deps Container) *oscar.Server {
  165. logger := deps.logger.With("svc", "OSCAR")
  166. adminService := foodgroup.NewAdminService(
  167. deps.sqLiteUserStore,
  168. deps.sqLiteUserStore,
  169. deps.sqLiteUserStore,
  170. deps.inMemorySessionManager,
  171. deps.inMemorySessionManager,
  172. deps.logger,
  173. )
  174. authService := foodgroup.NewAuthService(
  175. deps.cfg,
  176. deps.inMemorySessionManager,
  177. deps.inMemorySessionManager,
  178. deps.chatSessionManager,
  179. deps.sqLiteUserStore,
  180. deps.hmacCookieBaker,
  181. deps.chatSessionManager,
  182. deps.sqLiteUserStore,
  183. deps.rateLimitClasses,
  184. )
  185. bartService := foodgroup.NewBARTService(
  186. logger,
  187. deps.sqLiteUserStore,
  188. deps.inMemorySessionManager,
  189. deps.sqLiteUserStore,
  190. deps.inMemorySessionManager,
  191. )
  192. buddyService := foodgroup.NewBuddyService(
  193. deps.inMemorySessionManager,
  194. deps.sqLiteUserStore,
  195. deps.sqLiteUserStore,
  196. deps.inMemorySessionManager,
  197. deps.sqLiteUserStore,
  198. )
  199. chatService := foodgroup.NewChatService(deps.chatSessionManager)
  200. chatNavService := foodgroup.NewChatNavService(logger, deps.sqLiteUserStore)
  201. feedbagService := foodgroup.NewFeedbagService(
  202. logger,
  203. deps.inMemorySessionManager,
  204. deps.sqLiteUserStore,
  205. deps.sqLiteUserStore,
  206. deps.sqLiteUserStore,
  207. deps.inMemorySessionManager,
  208. )
  209. permitDenyService := foodgroup.NewPermitDenyService(
  210. deps.sqLiteUserStore,
  211. deps.sqLiteUserStore,
  212. deps.sqLiteUserStore,
  213. deps.inMemorySessionManager,
  214. deps.inMemorySessionManager,
  215. )
  216. icbmService := foodgroup.NewICBMService(
  217. deps.sqLiteUserStore,
  218. deps.inMemorySessionManager,
  219. deps.sqLiteUserStore,
  220. deps.sqLiteUserStore,
  221. deps.inMemorySessionManager,
  222. deps.snacRateLimits,
  223. )
  224. icqService := foodgroup.NewICQService(deps.inMemorySessionManager, deps.sqLiteUserStore, deps.sqLiteUserStore,
  225. logger, deps.inMemorySessionManager, deps.sqLiteUserStore)
  226. locateService := foodgroup.NewLocateService(
  227. deps.sqLiteUserStore,
  228. deps.inMemorySessionManager,
  229. deps.sqLiteUserStore,
  230. deps.sqLiteUserStore,
  231. deps.inMemorySessionManager,
  232. )
  233. oServiceService := foodgroup.NewOServiceService(
  234. deps.cfg,
  235. deps.inMemorySessionManager,
  236. logger,
  237. deps.hmacCookieBaker,
  238. deps.sqLiteUserStore,
  239. deps.sqLiteUserStore,
  240. deps.inMemorySessionManager,
  241. deps.sqLiteUserStore,
  242. deps.rateLimitClasses,
  243. deps.snacRateLimits,
  244. deps.chatSessionManager,
  245. )
  246. userLookupService := foodgroup.NewUserLookupService(deps.sqLiteUserStore)
  247. statsService := foodgroup.NewStatsService()
  248. oDirService := foodgroup.NewODirService(logger, deps.sqLiteUserStore)
  249. if err := deps.sqLiteUserStore.ClearBuddyListRegistry(context.Background()); err != nil {
  250. panic(err)
  251. }
  252. return oscar.NewServer(
  253. authService,
  254. deps.sqLiteUserStore,
  255. deps.chatSessionManager,
  256. buddyService,
  257. logger,
  258. oServiceService,
  259. oscar.Handler{
  260. AdminService: adminService,
  261. BARTService: bartService,
  262. BuddyService: buddyService,
  263. ChatNavService: chatNavService,
  264. ChatService: chatService,
  265. FeedbagService: feedbagService,
  266. ICBMService: icbmService,
  267. ICQService: icqService,
  268. LocateService: locateService,
  269. ODirService: oDirService,
  270. OServiceService: oServiceService,
  271. PermitDenyService: permitDenyService,
  272. StatsService: statsService,
  273. UserLookupService: userLookupService,
  274. RouteLogger: middleware.RouteLogger{
  275. Logger: logger,
  276. },
  277. }.Handle,
  278. oServiceService,
  279. deps.snacRateLimits,
  280. oscar.NewIPRateLimiter(rate.Every(1*time.Minute), 10, 1*time.Minute),
  281. deps.Listeners,
  282. )
  283. }
  284. // KerberosAPI creates an HTTP server for the Kerberos server.
  285. func KerberosAPI(deps Container) *kerberos.Server {
  286. logger := deps.logger.With("svc", "kerberos")
  287. authService := foodgroup.NewAuthService(deps.cfg, deps.inMemorySessionManager, deps.inMemorySessionManager, deps.chatSessionManager, deps.sqLiteUserStore, deps.hmacCookieBaker, deps.chatSessionManager, deps.sqLiteUserStore, deps.rateLimitClasses)
  288. return kerberos.NewKerberosServer(deps.Listeners, logger, authService)
  289. }
  290. // MgmtAPI creates an HTTP server for the management API.
  291. func MgmtAPI(deps Container) *http.Server {
  292. bld := config.Build{
  293. Version: version,
  294. Commit: commit,
  295. Date: date,
  296. }
  297. logger := deps.logger.With("svc", "API")
  298. return http.NewManagementAPI(bld, deps.cfg.APIListener, deps.sqLiteUserStore, deps.inMemorySessionManager, deps.sqLiteUserStore,
  299. deps.sqLiteUserStore, deps.chatSessionManager, deps.sqLiteUserStore, deps.inMemorySessionManager,
  300. deps.sqLiteUserStore, deps.sqLiteUserStore, deps.sqLiteUserStore, deps.sqLiteUserStore, logger)
  301. }
  302. // TOC creates a TOC server.
  303. func TOC(deps Container) *toc.Server {
  304. logger := deps.logger.With("svc", "TOC")
  305. return toc.NewServer(
  306. strings.Split(deps.cfg.TOCListeners, ","),
  307. logger,
  308. toc.OSCARProxy{
  309. AdminService: foodgroup.NewAdminService(
  310. deps.sqLiteUserStore,
  311. deps.sqLiteUserStore,
  312. deps.sqLiteUserStore,
  313. deps.inMemorySessionManager,
  314. deps.inMemorySessionManager,
  315. deps.logger,
  316. ),
  317. AuthService: foodgroup.NewAuthService(
  318. deps.cfg,
  319. deps.inMemorySessionManager,
  320. deps.inMemorySessionManager,
  321. deps.chatSessionManager,
  322. deps.sqLiteUserStore,
  323. deps.hmacCookieBaker,
  324. deps.chatSessionManager,
  325. deps.sqLiteUserStore,
  326. deps.rateLimitClasses,
  327. ),
  328. BuddyListRegistry: deps.sqLiteUserStore,
  329. BuddyService: foodgroup.NewBuddyService(
  330. deps.inMemorySessionManager,
  331. deps.sqLiteUserStore,
  332. deps.sqLiteUserStore,
  333. deps.inMemorySessionManager,
  334. deps.sqLiteUserStore,
  335. ),
  336. CookieBaker: deps.hmacCookieBaker,
  337. DirSearchService: foodgroup.NewODirService(logger, deps.sqLiteUserStore),
  338. ICBMService: foodgroup.NewICBMService(
  339. deps.sqLiteUserStore,
  340. deps.inMemorySessionManager,
  341. deps.sqLiteUserStore,
  342. deps.sqLiteUserStore,
  343. deps.inMemorySessionManager,
  344. deps.snacRateLimits,
  345. ),
  346. LocateService: foodgroup.NewLocateService(
  347. deps.sqLiteUserStore,
  348. deps.inMemorySessionManager,
  349. deps.sqLiteUserStore,
  350. deps.sqLiteUserStore,
  351. deps.inMemorySessionManager,
  352. ),
  353. Logger: logger,
  354. OServiceService: foodgroup.NewOServiceService(
  355. deps.cfg,
  356. deps.inMemorySessionManager,
  357. logger,
  358. deps.hmacCookieBaker,
  359. deps.sqLiteUserStore,
  360. deps.sqLiteUserStore,
  361. deps.inMemorySessionManager,
  362. deps.sqLiteUserStore,
  363. deps.rateLimitClasses,
  364. deps.snacRateLimits,
  365. deps.chatSessionManager,
  366. ),
  367. PermitDenyService: foodgroup.NewPermitDenyService(
  368. deps.sqLiteUserStore,
  369. deps.sqLiteUserStore,
  370. deps.sqLiteUserStore,
  371. deps.inMemorySessionManager,
  372. deps.inMemorySessionManager,
  373. ),
  374. TOCConfigStore: deps.sqLiteUserStore,
  375. ChatService: foodgroup.NewChatService(deps.chatSessionManager),
  376. ChatNavService: foodgroup.NewChatNavService(logger, deps.sqLiteUserStore),
  377. SNACRateLimits: deps.snacRateLimits,
  378. HTTPIPRateLimiter: toc.NewIPRateLimiter(rate.Every(1*time.Minute), 10, 1*time.Minute),
  379. },
  380. toc.NewIPRateLimiter(rate.Every(1*time.Minute), 10, 1*time.Minute),
  381. )
  382. }