factory.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. "log/slog"
  6. "net"
  7. "time"
  8. "github.com/kelseyhightower/envconfig"
  9. "golang.org/x/time/rate"
  10. "github.com/mk6i/retro-aim-server/config"
  11. "github.com/mk6i/retro-aim-server/foodgroup"
  12. "github.com/mk6i/retro-aim-server/server/http"
  13. "github.com/mk6i/retro-aim-server/server/oscar"
  14. "github.com/mk6i/retro-aim-server/server/oscar/handler"
  15. "github.com/mk6i/retro-aim-server/server/oscar/middleware"
  16. "github.com/mk6i/retro-aim-server/server/toc"
  17. "github.com/mk6i/retro-aim-server/state"
  18. "github.com/mk6i/retro-aim-server/wire"
  19. )
  20. // Container groups together common dependencies.
  21. type Container struct {
  22. cfg config.Config
  23. chatSessionManager *state.InMemoryChatSessionManager
  24. hmacCookieBaker state.HMACCookieBaker
  25. inMemorySessionManager *state.InMemorySessionManager
  26. logger *slog.Logger
  27. rateLimitClasses wire.RateLimitClasses
  28. snacRateLimits wire.SNACRateLimits
  29. sqLiteUserStore *state.SQLiteUserStore
  30. }
  31. // MakeCommonDeps creates common dependencies used by the food group services.
  32. func MakeCommonDeps() (Container, error) {
  33. c := Container{}
  34. err := envconfig.Process("", &c.cfg)
  35. if err != nil {
  36. return c, fmt.Errorf("unable to process app config: %s\n", err.Error())
  37. }
  38. if c.cfg.OSCARHost == "0.0.0.0" {
  39. return c, errors.New("invalid config: OSCAR_HOST cannot be set to " +
  40. "the 'all interfaces' IP (0.0.0.0). it must be a specific IP " +
  41. "address or hostname reachable by AIM/ICQ clients")
  42. }
  43. c.sqLiteUserStore, err = state.NewSQLiteUserStore(c.cfg.DBPath)
  44. if err != nil {
  45. return c, fmt.Errorf("unable to create feedbag store: %s\n", err.Error())
  46. }
  47. c.hmacCookieBaker, err = state.NewHMACCookieBaker()
  48. if err != nil {
  49. return c, fmt.Errorf("unable to create HMAC cookie baker: %s\n", err.Error())
  50. }
  51. c.logger = middleware.NewLogger(c.cfg)
  52. c.inMemorySessionManager = state.NewInMemorySessionManager(c.logger)
  53. c.chatSessionManager = state.NewInMemoryChatSessionManager(c.logger)
  54. c.rateLimitClasses = wire.DefaultRateLimitClasses()
  55. c.snacRateLimits = wire.DefaultSNACRateLimits()
  56. return c, nil
  57. }
  58. // Admin creates an OSCAR server for the Admin food group.
  59. func Admin(deps Container) oscar.AdminServer {
  60. logger := deps.logger.With("svc", "ADMIN")
  61. adminService := foodgroup.NewAdminService(
  62. deps.sqLiteUserStore,
  63. deps.sqLiteUserStore,
  64. deps.sqLiteUserStore,
  65. deps.inMemorySessionManager,
  66. deps.inMemorySessionManager,
  67. deps.logger,
  68. )
  69. authService := foodgroup.NewAuthService(
  70. deps.cfg,
  71. deps.inMemorySessionManager,
  72. deps.chatSessionManager,
  73. deps.sqLiteUserStore,
  74. deps.hmacCookieBaker,
  75. deps.chatSessionManager,
  76. deps.sqLiteUserStore,
  77. deps.inMemorySessionManager,
  78. deps.rateLimitClasses,
  79. )
  80. oServiceService := foodgroup.NewOServiceServiceForAdmin(
  81. deps.cfg,
  82. logger,
  83. deps.inMemorySessionManager,
  84. deps.sqLiteUserStore,
  85. deps.inMemorySessionManager,
  86. deps.sqLiteUserStore,
  87. deps.rateLimitClasses,
  88. deps.snacRateLimits,
  89. )
  90. return oscar.AdminServer{
  91. AuthService: authService,
  92. Config: deps.cfg,
  93. Handler: handler.NewAdminRouter(handler.Handlers{
  94. AdminHandler: handler.NewAdminHandler(logger, adminService),
  95. OServiceHandler: handler.NewOServiceHandler(logger, oServiceService),
  96. }),
  97. Logger: logger,
  98. OnlineNotifier: oServiceService,
  99. ListenAddr: net.JoinHostPort("", deps.cfg.AdminPort),
  100. RateLimitUpdater: oServiceService,
  101. SNACRateLimits: deps.snacRateLimits,
  102. }
  103. }
  104. // Alert creates an OSCAR server for the Alert food group.
  105. func Alert(deps Container) oscar.BOSServer {
  106. logger := deps.logger.With("svc", "ALERT")
  107. sessionManager := state.NewInMemorySessionManager(logger)
  108. authService := foodgroup.NewAuthService(
  109. deps.cfg,
  110. sessionManager,
  111. deps.chatSessionManager,
  112. deps.sqLiteUserStore,
  113. deps.hmacCookieBaker,
  114. deps.chatSessionManager,
  115. deps.sqLiteUserStore,
  116. nil,
  117. deps.rateLimitClasses,
  118. )
  119. oServiceService := foodgroup.NewOServiceServiceForAlert(
  120. deps.cfg,
  121. logger,
  122. sessionManager,
  123. deps.sqLiteUserStore,
  124. sessionManager,
  125. deps.sqLiteUserStore,
  126. deps.rateLimitClasses,
  127. deps.snacRateLimits,
  128. )
  129. return oscar.BOSServer{
  130. AuthService: authService,
  131. Config: deps.cfg,
  132. Handler: handler.NewAlertRouter(handler.Handlers{
  133. AlertHandler: handler.NewAlertHandler(logger),
  134. OServiceHandler: handler.NewOServiceHandler(logger, oServiceService),
  135. }),
  136. Logger: logger,
  137. OnlineNotifier: oServiceService,
  138. ListenAddr: net.JoinHostPort("", deps.cfg.AlertPort),
  139. RateLimitUpdater: oServiceService,
  140. SNACRateLimits: deps.snacRateLimits,
  141. }
  142. }
  143. // Auth creates an OSCAR server for the Auth food group.
  144. func Auth(deps Container) oscar.AuthServer {
  145. logger := deps.logger.With("svc", "AUTH")
  146. authHandler := foodgroup.NewAuthService(
  147. deps.cfg,
  148. deps.inMemorySessionManager,
  149. deps.chatSessionManager,
  150. deps.sqLiteUserStore,
  151. deps.hmacCookieBaker,
  152. deps.chatSessionManager,
  153. deps.sqLiteUserStore,
  154. nil,
  155. deps.rateLimitClasses,
  156. )
  157. return oscar.AuthServer{
  158. AuthService: authHandler,
  159. Config: deps.cfg,
  160. Logger: logger,
  161. IPRateLimiter: oscar.NewIPRateLimiter(rate.Every(1*time.Minute), 10, 1*time.Minute),
  162. }
  163. }
  164. // BART creates an OSCAR server for the BART food group.
  165. func BART(deps Container) oscar.BOSServer {
  166. logger := deps.logger.With("svc", "BART")
  167. sessionManager := state.NewInMemorySessionManager(logger)
  168. bartService := foodgroup.NewBARTService(
  169. logger,
  170. deps.sqLiteUserStore,
  171. sessionManager,
  172. deps.sqLiteUserStore,
  173. sessionManager,
  174. )
  175. authService := foodgroup.NewAuthService(
  176. deps.cfg,
  177. sessionManager,
  178. deps.chatSessionManager,
  179. deps.sqLiteUserStore,
  180. deps.hmacCookieBaker,
  181. deps.chatSessionManager,
  182. deps.sqLiteUserStore,
  183. nil,
  184. deps.rateLimitClasses,
  185. )
  186. oServiceService := foodgroup.NewOServiceServiceForBART(
  187. deps.cfg,
  188. logger,
  189. sessionManager,
  190. deps.sqLiteUserStore,
  191. sessionManager,
  192. deps.sqLiteUserStore,
  193. deps.rateLimitClasses,
  194. deps.snacRateLimits,
  195. )
  196. return oscar.BOSServer{
  197. AuthService: authService,
  198. Config: deps.cfg,
  199. Handler: handler.NewBARTRouter(handler.Handlers{
  200. BARTHandler: handler.NewBARTHandler(logger, bartService),
  201. OServiceHandler: handler.NewOServiceHandler(logger, oServiceService),
  202. }),
  203. ListenAddr: net.JoinHostPort("", deps.cfg.BARTPort),
  204. Logger: logger,
  205. OnlineNotifier: oServiceService,
  206. RateLimitUpdater: oServiceService,
  207. SNACRateLimits: deps.snacRateLimits,
  208. }
  209. }
  210. // BOS creates an OSCAR server for the BOS food group.
  211. func BOS(deps Container) oscar.BOSServer {
  212. logger := deps.logger.With("svc", "BOS")
  213. authService := foodgroup.NewAuthService(
  214. deps.cfg,
  215. deps.inMemorySessionManager,
  216. deps.chatSessionManager,
  217. deps.sqLiteUserStore,
  218. deps.hmacCookieBaker,
  219. deps.chatSessionManager,
  220. deps.sqLiteUserStore,
  221. nil,
  222. deps.rateLimitClasses,
  223. )
  224. bartService := foodgroup.NewBARTService(
  225. logger,
  226. deps.sqLiteUserStore,
  227. deps.inMemorySessionManager,
  228. deps.sqLiteUserStore,
  229. deps.inMemorySessionManager,
  230. )
  231. buddyService := foodgroup.NewBuddyService(
  232. deps.inMemorySessionManager,
  233. deps.sqLiteUserStore,
  234. deps.sqLiteUserStore,
  235. deps.inMemorySessionManager,
  236. deps.sqLiteUserStore,
  237. )
  238. chatNavService := foodgroup.NewChatNavService(logger, deps.sqLiteUserStore)
  239. feedbagService := foodgroup.NewFeedbagService(
  240. logger,
  241. deps.inMemorySessionManager,
  242. deps.sqLiteUserStore,
  243. deps.sqLiteUserStore,
  244. deps.sqLiteUserStore,
  245. deps.inMemorySessionManager,
  246. )
  247. permitDenyService := foodgroup.NewPermitDenyService(
  248. deps.sqLiteUserStore,
  249. deps.sqLiteUserStore,
  250. deps.sqLiteUserStore,
  251. deps.inMemorySessionManager,
  252. deps.inMemorySessionManager,
  253. )
  254. icbmService := foodgroup.NewICBMService(
  255. deps.sqLiteUserStore,
  256. deps.inMemorySessionManager,
  257. deps.sqLiteUserStore,
  258. deps.sqLiteUserStore,
  259. deps.inMemorySessionManager,
  260. deps.snacRateLimits,
  261. )
  262. icqService := foodgroup.NewICQService(deps.inMemorySessionManager, deps.sqLiteUserStore, deps.sqLiteUserStore,
  263. logger, deps.inMemorySessionManager, deps.sqLiteUserStore)
  264. locateService := foodgroup.NewLocateService(
  265. deps.sqLiteUserStore,
  266. deps.inMemorySessionManager,
  267. deps.sqLiteUserStore,
  268. deps.sqLiteUserStore,
  269. deps.inMemorySessionManager,
  270. )
  271. oServiceService := foodgroup.NewOServiceServiceForBOS(
  272. deps.cfg,
  273. deps.inMemorySessionManager,
  274. logger,
  275. deps.hmacCookieBaker,
  276. deps.sqLiteUserStore,
  277. deps.sqLiteUserStore,
  278. deps.inMemorySessionManager,
  279. deps.sqLiteUserStore,
  280. deps.rateLimitClasses,
  281. deps.snacRateLimits,
  282. )
  283. userLookupService := foodgroup.NewUserLookupService(deps.sqLiteUserStore)
  284. statsService := foodgroup.NewStatsService()
  285. return oscar.BOSServer{
  286. AuthService: authService,
  287. BuddyListRegistry: deps.sqLiteUserStore,
  288. Config: deps.cfg,
  289. DepartureNotifier: buddyService,
  290. ChatSessionManager: deps.chatSessionManager,
  291. Handler: handler.NewBOSRouter(handler.Handlers{
  292. AlertHandler: handler.NewAlertHandler(logger),
  293. BARTHandler: handler.NewBARTHandler(logger, bartService),
  294. BuddyHandler: handler.NewBuddyHandler(logger, buddyService),
  295. ChatNavHandler: handler.NewChatNavHandler(chatNavService, logger),
  296. FeedbagHandler: handler.NewFeedbagHandler(logger, feedbagService),
  297. ICQHandler: handler.NewICQHandler(logger, icqService),
  298. ICBMHandler: handler.NewICBMHandler(logger, icbmService),
  299. LocateHandler: handler.NewLocateHandler(locateService, logger),
  300. OServiceHandler: handler.NewOServiceHandler(logger, oServiceService),
  301. PermitDenyHandler: handler.NewPermitDenyHandler(logger, permitDenyService),
  302. StatsHandler: handler.NewStatsHandler(logger, statsService),
  303. UserLookupHandler: handler.NewUserLookupHandler(logger, userLookupService),
  304. }),
  305. Logger: logger,
  306. OnlineNotifier: oServiceService,
  307. ListenAddr: net.JoinHostPort("", deps.cfg.BOSPort),
  308. RateLimitUpdater: oServiceService,
  309. SNACRateLimits: deps.snacRateLimits,
  310. }
  311. }
  312. // Chat creates an OSCAR server for the Chat food group.
  313. func Chat(deps Container) oscar.ChatServer {
  314. logger := deps.logger.With("svc", "CHAT")
  315. sessionManager := state.NewInMemorySessionManager(logger)
  316. authService := foodgroup.NewAuthService(
  317. deps.cfg,
  318. sessionManager,
  319. deps.chatSessionManager,
  320. deps.sqLiteUserStore,
  321. deps.hmacCookieBaker,
  322. deps.chatSessionManager,
  323. deps.sqLiteUserStore,
  324. nil,
  325. deps.rateLimitClasses,
  326. )
  327. chatService := foodgroup.NewChatService(deps.chatSessionManager)
  328. oServiceService := foodgroup.NewOServiceServiceForChat(
  329. deps.cfg,
  330. logger,
  331. sessionManager,
  332. deps.sqLiteUserStore,
  333. deps.chatSessionManager,
  334. deps.sqLiteUserStore,
  335. sessionManager,
  336. deps.sqLiteUserStore,
  337. deps.rateLimitClasses,
  338. deps.snacRateLimits,
  339. )
  340. return oscar.ChatServer{
  341. AuthService: authService,
  342. Config: deps.cfg,
  343. Handler: handler.NewChatRouter(handler.Handlers{
  344. ChatHandler: handler.NewChatHandler(logger, chatService),
  345. OServiceHandler: handler.NewOServiceHandler(logger, oServiceService),
  346. }),
  347. Logger: logger,
  348. OnlineNotifier: oServiceService,
  349. SNACRateLimits: deps.snacRateLimits,
  350. RateLimitUpdater: oServiceService,
  351. }
  352. }
  353. // ChatNav creates an OSCAR server for the ChatNav food group.
  354. func ChatNav(deps Container) oscar.BOSServer {
  355. logger := deps.logger.With("svc", "CHAT_NAV")
  356. sessionManager := state.NewInMemorySessionManager(logger)
  357. authService := foodgroup.NewAuthService(
  358. deps.cfg,
  359. sessionManager,
  360. deps.chatSessionManager,
  361. deps.sqLiteUserStore,
  362. deps.hmacCookieBaker,
  363. deps.chatSessionManager,
  364. deps.sqLiteUserStore,
  365. nil,
  366. deps.rateLimitClasses,
  367. )
  368. chatNavService := foodgroup.NewChatNavService(logger, deps.sqLiteUserStore)
  369. oServiceService := foodgroup.NewOServiceServiceForChatNav(
  370. deps.cfg,
  371. logger,
  372. sessionManager,
  373. deps.sqLiteUserStore,
  374. sessionManager,
  375. deps.sqLiteUserStore,
  376. deps.rateLimitClasses,
  377. deps.snacRateLimits,
  378. )
  379. return oscar.BOSServer{
  380. AuthService: authService,
  381. Config: deps.cfg,
  382. Handler: handler.NewChatNavRouter(handler.Handlers{
  383. ChatNavHandler: handler.NewChatNavHandler(chatNavService, logger),
  384. OServiceHandler: handler.NewOServiceHandler(logger, oServiceService),
  385. }),
  386. Logger: logger,
  387. OnlineNotifier: oServiceService,
  388. ListenAddr: net.JoinHostPort("", deps.cfg.ChatNavPort),
  389. RateLimitUpdater: oServiceService,
  390. SNACRateLimits: deps.snacRateLimits,
  391. }
  392. }
  393. // MgmtAPI creates an HTTP server for the management API.
  394. func MgmtAPI(deps Container) *http.Server {
  395. bld := config.Build{
  396. Version: version,
  397. Commit: commit,
  398. Date: date,
  399. }
  400. return http.NewManagementAPI(bld, deps.cfg, deps.sqLiteUserStore, deps.inMemorySessionManager, deps.sqLiteUserStore,
  401. deps.sqLiteUserStore, deps.chatSessionManager, deps.sqLiteUserStore, deps.inMemorySessionManager,
  402. deps.sqLiteUserStore, deps.sqLiteUserStore, deps.sqLiteUserStore, deps.sqLiteUserStore, deps.logger)
  403. }
  404. // ODir creates an OSCAR server for the ODir food group.
  405. func ODir(deps Container) oscar.BOSServer {
  406. logger := deps.logger.With("svc", "ODIR")
  407. sessionManager := state.NewInMemorySessionManager(logger)
  408. authService := foodgroup.NewAuthService(
  409. deps.cfg,
  410. sessionManager,
  411. deps.chatSessionManager,
  412. deps.sqLiteUserStore,
  413. deps.hmacCookieBaker,
  414. deps.chatSessionManager,
  415. deps.sqLiteUserStore,
  416. nil,
  417. deps.rateLimitClasses,
  418. )
  419. oServiceService := foodgroup.NewOServiceServiceForODir(
  420. deps.cfg,
  421. logger,
  422. deps.rateLimitClasses,
  423. deps.snacRateLimits,
  424. )
  425. oDirService := foodgroup.NewODirService(logger, deps.sqLiteUserStore)
  426. return oscar.BOSServer{
  427. AuthService: authService,
  428. Config: deps.cfg,
  429. Handler: handler.NewODirRouter(handler.Handlers{
  430. OServiceHandler: handler.NewOServiceHandler(logger, oServiceService),
  431. ODirHandler: handler.NewODirHandler(logger, oDirService),
  432. }),
  433. Logger: logger,
  434. OnlineNotifier: oServiceService,
  435. ListenAddr: net.JoinHostPort("", deps.cfg.ODirPort),
  436. RateLimitUpdater: oServiceService,
  437. SNACRateLimits: deps.snacRateLimits,
  438. }
  439. }
  440. // TOC creates a TOC server.
  441. func TOC(deps Container) toc.Server {
  442. logger := deps.logger.With("svc", "TOC")
  443. sessionManager := state.NewInMemorySessionManager(logger)
  444. return toc.Server{
  445. Logger: logger,
  446. ListenAddr: net.JoinHostPort(deps.cfg.TOCHost, deps.cfg.TOCPort),
  447. BOSProxy: toc.OSCARProxy{
  448. AdminService: foodgroup.NewAdminService(
  449. deps.sqLiteUserStore,
  450. deps.sqLiteUserStore,
  451. deps.sqLiteUserStore,
  452. deps.inMemorySessionManager,
  453. deps.inMemorySessionManager,
  454. deps.logger,
  455. ),
  456. AuthService: foodgroup.NewAuthService(
  457. deps.cfg,
  458. deps.inMemorySessionManager,
  459. deps.chatSessionManager,
  460. deps.sqLiteUserStore,
  461. deps.hmacCookieBaker,
  462. deps.chatSessionManager,
  463. deps.sqLiteUserStore,
  464. nil,
  465. deps.rateLimitClasses,
  466. ),
  467. BuddyListRegistry: deps.sqLiteUserStore,
  468. BuddyService: foodgroup.NewBuddyService(
  469. deps.inMemorySessionManager,
  470. deps.sqLiteUserStore,
  471. deps.sqLiteUserStore,
  472. deps.inMemorySessionManager,
  473. deps.sqLiteUserStore,
  474. ),
  475. CookieBaker: deps.hmacCookieBaker,
  476. DirSearchService: foodgroup.NewODirService(logger, deps.sqLiteUserStore),
  477. ICBMService: foodgroup.NewICBMService(
  478. deps.sqLiteUserStore,
  479. deps.inMemorySessionManager,
  480. deps.sqLiteUserStore,
  481. deps.sqLiteUserStore,
  482. deps.inMemorySessionManager,
  483. deps.snacRateLimits,
  484. ),
  485. LocateService: foodgroup.NewLocateService(
  486. deps.sqLiteUserStore,
  487. deps.inMemorySessionManager,
  488. deps.sqLiteUserStore,
  489. deps.sqLiteUserStore,
  490. deps.inMemorySessionManager,
  491. ),
  492. Logger: logger,
  493. OServiceServiceBOS: foodgroup.NewOServiceServiceForBOS(
  494. deps.cfg,
  495. deps.inMemorySessionManager,
  496. logger,
  497. deps.hmacCookieBaker,
  498. deps.sqLiteUserStore,
  499. deps.sqLiteUserStore,
  500. deps.inMemorySessionManager,
  501. deps.sqLiteUserStore,
  502. deps.rateLimitClasses,
  503. deps.snacRateLimits,
  504. ),
  505. PermitDenyService: foodgroup.NewPermitDenyService(
  506. deps.sqLiteUserStore,
  507. deps.sqLiteUserStore,
  508. deps.sqLiteUserStore,
  509. deps.inMemorySessionManager,
  510. deps.inMemorySessionManager,
  511. ),
  512. TOCConfigStore: deps.sqLiteUserStore,
  513. ChatService: foodgroup.NewChatService(deps.chatSessionManager),
  514. OServiceServiceChat: foodgroup.NewOServiceServiceForChat(
  515. deps.cfg,
  516. logger,
  517. sessionManager,
  518. deps.sqLiteUserStore,
  519. deps.chatSessionManager,
  520. deps.sqLiteUserStore,
  521. sessionManager,
  522. deps.sqLiteUserStore,
  523. deps.rateLimitClasses,
  524. deps.snacRateLimits,
  525. ),
  526. ChatNavService: foodgroup.NewChatNavService(logger, deps.sqLiteUserStore),
  527. SNACRateLimits: deps.snacRateLimits,
  528. HTTPIPRateLimiter: toc.NewIPRateLimiter(rate.Every(1*time.Minute), 10, 1*time.Minute),
  529. },
  530. LoginIPRateLimiter: toc.NewIPRateLimiter(rate.Every(10*time.Minute), 10, 20*time.Minute),
  531. }
  532. }