@@ -2,6 +2,7 @@ package handler
22
33import (
44 "context"
5+ "errors"
56 "crypto/rand"
67 "crypto/subtle"
78 "encoding/binary"
@@ -22,6 +23,18 @@ import (
2223 db "github.com/multica-ai/multica/server/pkg/db/generated"
2324)
2425
26+ // SignupError represents signup restriction errors
27+ type SignupError struct {
28+ Message string
29+ }
30+
31+ func (e SignupError ) Error () string {
32+ return e .Message
33+ }
34+
35+ var ErrSignupProhibited = SignupError {Message : "user registration is disabled on this self-hosted instance" }
36+ var ErrEmailNotAllowed = SignupError {Message : "email address or domain not allowed on this instance" }
37+
2538type UserResponse struct {
2639 ID string `json:"id"`
2740 Name string `json:"name"`
@@ -78,23 +91,68 @@ func (h *Handler) issueJWT(user db.User) (string, error) {
7891
7992func (h * Handler ) findOrCreateUser (ctx context.Context , email string ) (db.User , error ) {
8093 user , err := h .Queries .GetUserByEmail (ctx , email )
81- if err != nil {
82- if ! isNotFound (err ) {
83- return db.User {}, err
84- }
85- name := email
86- if at := strings .Index (email , "@" ); at > 0 {
87- name = email [:at ]
94+ if err == nil {
95+ return user , nil
96+ }
97+ if ! isNotFound (err ) {
98+ return db.User {}, err
99+ }
100+
101+ // New user creation path. Check if signups are allowed.
102+ if err := h .checkSignupAllowed (email ); err != nil {
103+ return db.User {}, err
104+ }
105+
106+ name := email
107+ if at := strings .Index (email , "@" ); at > 0 {
108+ name = email [:at ]
109+ }
110+ return h .Queries .CreateUser (ctx , db.CreateUserParams {
111+ Name : name ,
112+ Email : email ,
113+ })
114+ }
115+
116+ func (h * Handler ) checkSignupAllowed (email string ) error {
117+ if os .Getenv ("ALLOW_SIGNUP" ) == "false" {
118+ return ErrSignupProhibited
119+ }
120+
121+ allowedDomainsStr := os .Getenv ("ALLOWED_EMAIL_DOMAINS" )
122+ allowedEmailsStr := os .Getenv ("ALLOWED_EMAILS" )
123+
124+ if allowedDomainsStr == "" && allowedEmailsStr == "" {
125+ return nil
126+ }
127+
128+ allowed := false
129+ emailLower := strings .ToLower (email )
130+
131+ if allowedDomainsStr != "" {
132+ for _ , domain := range strings .Split (allowedDomainsStr , "," ) {
133+ domain = strings .TrimSpace (domain )
134+ if domain != "" && strings .HasSuffix (emailLower , "@" + strings .ToLower (domain )) {
135+ allowed = true
136+ break
137+ }
88138 }
89- user , err = h .Queries .CreateUser (ctx , db.CreateUserParams {
90- Name : name ,
91- Email : email ,
92- })
93- if err != nil {
94- return db.User {}, err
139+ }
140+
141+ if ! allowed && allowedEmailsStr != "" {
142+ for _ , allowedEmail := range strings .Split (allowedEmailsStr , "," ) {
143+ allowedEmail = strings .TrimSpace (allowedEmail )
144+ if allowedEmail != "" && strings .EqualFold (emailLower , allowedEmail ) {
145+ allowed = true
146+ break
147+ }
95148 }
96149 }
97- return user , nil
150+
151+ if ! allowed {
152+ return ErrEmailNotAllowed
153+ }
154+
155+ return nil
98156}
99157
100158func (h * Handler ) SendCode (w http.ResponseWriter , r * http.Request ) {
@@ -110,6 +168,19 @@ func (h *Handler) SendCode(w http.ResponseWriter, r *http.Request) {
110168 return
111169 }
112170
171+ // Short-circuit: If not already a user, check if signups are allowed for this email
172+ _ , err := h .Queries .GetUserByEmail (r .Context (), email )
173+ if err != nil && isNotFound (err ) {
174+ if err := h .checkSignupAllowed (email ); err != nil {
175+ msg := "user registration is disabled"
176+ if signupErr , ok := err .(SignupError ); ok {
177+ msg = signupErr .Message
178+ }
179+ writeError (w , http .StatusForbidden , msg )
180+ return
181+ }
182+ }
183+
113184 // Rate limit: max 1 code per 60 seconds per email
114185 latest , err := h .Queries .GetLatestCodeByEmail (r .Context (), email )
115186 if err == nil && time .Since (latest .CreatedAt .Time ) < 60 * time .Second {
@@ -180,6 +251,11 @@ func (h *Handler) VerifyCode(w http.ResponseWriter, r *http.Request) {
180251
181252 user , err := h .findOrCreateUser (r .Context (), email )
182253 if err != nil {
254+ var signupErr SignupError
255+ if errors .As (err , & signupErr ) {
256+ writeError (w , http .StatusForbidden , signupErr .Error ())
257+ return
258+ }
183259 writeError (w , http .StatusInternalServerError , "failed to create user" )
184260 return
185261 }
@@ -336,6 +412,11 @@ func (h *Handler) GoogleLogin(w http.ResponseWriter, r *http.Request) {
336412
337413 user , err := h .findOrCreateUser (r .Context (), email )
338414 if err != nil {
415+ var signupErr SignupError
416+ if errors .As (err , & signupErr ) {
417+ writeError (w , http .StatusForbidden , signupErr .Error ())
418+ return
419+ }
339420 writeError (w , http .StatusInternalServerError , "failed to create user" )
340421 return
341422 }
0 commit comments