@@ -16,6 +16,8 @@ import (
1616 "github.com/google/go-github/v82/github"
1717 "github.com/google/uuid"
1818
19+ adminv1 "xeiaso.net/v4/gen/techaro/thoth/auth/admin/v1"
20+
1921 "xeiaso.net/v4/cmd/sponsor-panel/templates"
2022)
2123
@@ -318,3 +320,82 @@ func renderInviteSuccess(w http.ResponseWriter, username, state string) {
318320func renderLogoSuccess (w http.ResponseWriter , company , issueURL string , issueNumber int ) {
319321 templates .LogoSuccess (company , issueURL , issueNumber ).Render (context .Background (), w )
320322}
323+
324+ // thothTokenHandler handles POST /thoth-token - issues a Thoth JWT for the user.
325+ func (s * Server ) thothTokenHandler (w http.ResponseWriter , r * http.Request ) {
326+ if r .Method != http .MethodPost {
327+ http .Error (w , "Method not allowed" , http .StatusMethodNotAllowed )
328+ return
329+ }
330+
331+ slog .Debug ("thothTokenHandler: processing token request" )
332+
333+ // Get user from session
334+ user , err := s .getSessionUser (r )
335+ if err != nil {
336+ slog .Error ("thothTokenHandler: failed to get session user" , "err" , err )
337+ renderError (w , "Authentication required" , http .StatusUnauthorized )
338+ return
339+ }
340+
341+ slog .Debug ("thothTokenHandler: authenticated user" , "user_id" , user .ID , "login" , user .Login )
342+
343+ // Check sponsorship tier (any active sponsorship)
344+ if ! user .IsSponsorAtTier (100 ) {
345+ slog .Error ("thothTokenHandler: user not a sponsor" , "user" , user .Login , "user_id" , user .ID )
346+ renderError (w , "Requires active sponsorship" , http .StatusForbidden )
347+ return
348+ }
349+
350+ // Create Thoth user if not already provisioned
351+ if user .ThothUserID == nil {
352+ if user .Email == "" {
353+ slog .Error ("thothTokenHandler: user has no email address" , "user_id" , user .ID , "login" , user .Login )
354+ renderError (w , "Email address required. Please update your profile." , http .StatusBadRequest )
355+ return
356+ }
357+
358+ slog .Debug ("thothTokenHandler: creating Thoth user" , "user_id" , user .ID , "login" , user .Login )
359+
360+ resp , err := s .thothClient .AdminUsers .Create (r .Context (), & adminv1.UsersServiceCreateRequest {
361+ EmailAddress : user .Email ,
362+ Name : user .Login ,
363+ CustomerId : user .Provider + ":" + user .Login ,
364+ })
365+ if err != nil {
366+ slog .Error ("thothTokenHandler: failed to create Thoth user" , "err" , err , "user_id" , user .ID )
367+ renderError (w , "Failed to create Thoth user: " + err .Error (), http .StatusInternalServerError )
368+ return
369+ }
370+
371+ thothID := resp .GetUser ().GetId ()
372+ user .ThothUserID = & thothID
373+
374+ if err := s .db .Save (user ).Error ; err != nil {
375+ slog .Error ("thothTokenHandler: failed to save Thoth user ID" , "err" , err , "user_id" , user .ID )
376+ renderError (w , "Failed to save Thoth user: " + err .Error (), http .StatusInternalServerError )
377+ return
378+ }
379+
380+ slog .Info ("thothTokenHandler: Thoth user created" , "user_id" , user .ID , "login" , user .Login , "thoth_user_id" , thothID )
381+ }
382+
383+ // Issue JWT
384+ slog .Debug ("thothTokenHandler: issuing JWT" , "user_id" , user .ID , "thoth_user_id" , * user .ThothUserID )
385+
386+ jwtResp , err := s .thothClient .AdminUsers .MakeJWT (r .Context (), & adminv1.UsersServiceMakeJWTRequest {
387+ UserId : * user .ThothUserID ,
388+ Comment : "sponsor-panel token for " + user .Login ,
389+ })
390+ if err != nil {
391+ slog .Error ("thothTokenHandler: failed to issue JWT" , "err" , err , "user_id" , user .ID )
392+ renderError (w , "Failed to issue token: " + err .Error (), http .StatusInternalServerError )
393+ return
394+ }
395+
396+ slog .Info ("thothTokenHandler: token issued" , "user_id" , user .ID , "login" , user .Login )
397+
398+ w .Header ().Set ("Content-Type" , "text/html" )
399+ w .WriteHeader (http .StatusOK )
400+ templates .ThothTokenSuccess (jwtResp .GetTokenInfo ().GetJwt ()).Render (context .Background (), w )
401+ }
0 commit comments