package boot import ( "context" "database/sql" "net/http" "time" "atlas9.dev/c/core/dbi" "atlas9.dev/c/core/iam" "atlas9.dev/c/core/throttle" "atlas9.dev/c/core/tokens" "atlas9.dev/c/demo/api" "atlas9.dev/c/demo/bots" "atlas9.dev/c/demo/lib" "atlas9.dev/c/demo/lib/domains" "atlas9.dev/c/demo/lib/sso" "atlas9.dev/c/demo/lib/users" "atlas9.dev/c/demo/mail" "atlas9.dev/c/demo/store" "atlas9.dev/c/demo/tasks" "atlas9.dev/c/iam/oidc_provider" ) type Server struct { handler http.Handler Mux *http.ServeMux TaskTables map[string]string Users dbi.Factory[iam.UserStore] Passwords dbi.Factory[iam.PasswordStore] Tenants dbi.Factory[iam.TenantStore] TenantMembers dbi.Factory[iam.TenantMemberStore] Roles dbi.Factory[iam.RoleStore] Grants dbi.Factory[iam.GrantStore] Groups dbi.Factory[iam.GroupStore] Sessions dbi.Factory[iam.SessionStore] TenantInvitations dbi.Factory[iam.TenantInvitationStore] Profiles dbi.Factory[*store.ProfileStore] Access dbi.Factory[lib.AccessStore] EmailVerificationTokens dbi.Factory[store.EmailVerificationTokenStore] Tasks dbi.Factory[tasks.Producer] PasswordResetTokens dbi.Factory[store.PasswordResetTokenStore] BotKeys dbi.Factory[bots.KeyStore] Bots dbi.Factory[bots.BotStore] Domains dbi.Factory[domains.Store] OauthProviders dbi.Factory[iam.OAuthStore] OauthTokens dbi.Factory[tokens.Store[oidc_provider.StateData]] SsoConfigs dbi.Factory[sso.Store] UserProvisioner users.Provisioner } func NewServer( baseURL string, db *sql.DB, mailer mail.Mailer, config *Config, ) *Server { guard := lib.ContextAccessGuard{} taskTables := map[string]string{ "Tasks.SendEmailVerification": "email_verification_tasks", "Tasks.SendPasswordReset": "password_reset_tasks", "Tasks.SendTenantInvitation": "tenant_invitation_tasks", "Domain_Verify": "domain_verification_tasks", } // TODO all these deps should be passed in, so that it's not sqlite specific? // or, should NewServer switch impls based on config only? s := &Server{ TaskTables: taskTables, Users: func(tx dbi.DBI) iam.UserStore { return store.NewSqliteUserStore(tx, guard) }, Passwords: func(tx dbi.DBI) iam.PasswordStore { return store.NewSqlitePasswordHashStore(tx, guard) }, Tenants: func(tx dbi.DBI) iam.TenantStore { return store.NewSqliteTenantStore(tx, guard) }, TenantMembers: func(tx dbi.DBI) iam.TenantMemberStore { return store.NewSqliteTenantMemberStore(tx, guard) }, Roles: func(tx dbi.DBI) iam.RoleStore { return store.NewSqliteRoleStore(tx, guard) }, Grants: func(tx dbi.DBI) iam.GrantStore { return store.NewSqliteGrantStore(tx, guard) }, Groups: func(tx dbi.DBI) iam.GroupStore { return store.NewSqliteGroupStore(tx, guard) }, Sessions: func(tx dbi.DBI) iam.SessionStore { return store.NewSqliteSessionStore(tx) // TODO guard }, TenantInvitations: func(tx dbi.DBI) iam.TenantInvitationStore { return store.NewSqliteTenantInvitationStore(tx, guard) }, Profiles: func(tx dbi.DBI) *store.ProfileStore { return store.NewProfileStore(tx, guard) }, Access: func(tx dbi.DBI) lib.AccessStore { return store.NewSqliteAccessStore(tx) }, EmailVerificationTokens: func(tx dbi.DBI) store.EmailVerificationTokenStore { return store.NewEmailVerificationTokenStore(tx) }, PasswordResetTokens: func(tx dbi.DBI) store.PasswordResetTokenStore { return store.NewPasswordResetTokenStore(tx) }, BotKeys: func(tx dbi.DBI) bots.KeyStore { return store.NewSqliteBotKeyStore(tx, guard) }, Bots: func(tx dbi.DBI) bots.BotStore { return store.NewSqliteBotStore(tx, guard) }, Tasks: func(tx dbi.DBI) tasks.Producer { return tasks.NewSqliteProducer(tx, taskTables) }, Domains: func(tx dbi.DBI) domains.Store { return store.NewSqliteDomainStore(tx, guard) }, OauthProviders: func(tx dbi.DBI) iam.OAuthStore { return store.NewSqliteOAuthStore(tx) }, OauthTokens: func(tx dbi.DBI) tokens.Store[oidc_provider.StateData] { return store.NewSqliteTokenStore[oidc_provider.StateData]( tx, "oidc_state", 5*time.Minute, tokens.RandomString(20), tokens.RandomString(20), ) }, SsoConfigs: func(d dbi.DBI) sso.Store { return store.NewSqliteSsoStore(d, guard) }, } s.UserProvisioner = users.Provisioner{ Tenants: s.Tenants, TenantMembers: s.TenantMembers, Grants: s.Grants, Profiles: s.Profiles, } sessionManager := &iam.SessionMan{ CookieName: config.Session.Name, SecureCookie: config.Session.Secure, MaxAge: config.Session.Lifetime, Store: s.Sessions, } accountThrottle := api.NewAccountThrottle(throttle.NewMemoryBucket()) mux := http.NewServeMux() api.HealthApi(mux) api.AccountApi(mux, &api.AccountImpl{ DB: db, Users: s.Users, Passwords: s.Passwords, Provisioner: s.UserProvisioner, Tasks: s.Tasks, EmailVerificationTokens: s.EmailVerificationTokens, PasswordResetTokens: s.PasswordResetTokens, Throttle: accountThrottle, }) api.TenantsApi(mux, &api.TenantsImpl{ DB: db, Guard: guard, Tenants: s.Tenants, Members: s.TenantMembers, Grants: s.Grants, }) api.TenantMembersApi(mux, &api.TenantMembersImpl{ DB: db, Guard: guard, Members: s.TenantMembers, }) api.TenantInvitationsApi(mux, &api.TenantInvitationsImpl{ DB: db, Guard: guard, Invitations: s.TenantInvitations, Members: s.TenantMembers, Grants: s.Grants, Users: s.Users, Tasks: s.Tasks, }) api.IdentityApi(mux, &api.IdentityImpl{ DB: db, Users: s.Users, Passwords: s.Passwords, SessionMan: sessionManager, Throttle: accountThrottle, }) api.GroupsApi(mux, &api.GroupsImpl{ DB: db, Guard: guard, Groups: s.Groups, }) api.RolesApi(mux, &api.RolesImpl{ DB: db, Guard: guard, Roles: s.Roles, }) api.GrantsApi(mux, &api.GrantsImpl{ DB: db, Guard: guard, Grants: s.Grants, }) api.ProfilesApi(mux, &api.ProfilesImpl{ DB: db, Users: s.Users, Profiles: s.Profiles, }) api.BotsApi(mux, &api.BotsImpl{ DB: db, Guard: guard, Store: s.Bots, }) api.BotKeysApi(mux, &api.BotKeysImpl{ DB: db, KeyStore: s.BotKeys, }) api.TasksApi(mux, &api.TasksImpl{ Mailer: mailer, BaseURL: baseURL, }) api.DomainApi(mux, &api.DomainImpl{ DB: db, Store: s.Domains, Tasks: s.Tasks, }) oidcDeps := oidc_provider.Deps{ DB: db, Tokens: s.OauthTokens, Users: s.Users, OAuth: s.OauthProviders, Provisioner: s.UserProvisioner, SessionMan: sessionManager, } initOauth(context.TODO(), config, mux, oidcDeps) api.SsoApi(mux, &api.SsoImpl{ DB: db, Domains: s.Domains, SsoConfigs: s.SsoConfigs, OidcDeps: oidcDeps, }) s.Mux = mux // Cross-origin protection (CSRF) protection := http.NewCrossOriginProtection() // TODO document why this is allowed protection.AddInsecureBypassPattern("POST /auth/{provider}/callback") iamMiddleware := api.IamMiddleware{ DB: db, Access: s.Access, Sessions: s.Sessions, Keys: s.BotKeys, SessionMan: sessionManager, } // middleware. last listed here is the first middleware processed. s.handler = iamMiddleware.Handler(mux) s.handler = protection.Handler(s.handler) return s } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { s.handler.ServeHTTP(w, r) }