github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/instances/client.go (about) 1 package instances 2 3 import ( 4 "net/http" 5 "strconv" 6 "time" 7 8 "github.com/cozy/cozy-stack/model/instance" 9 "github.com/cozy/cozy-stack/model/instance/lifecycle" 10 "github.com/cozy/cozy-stack/model/oauth" 11 "github.com/cozy/cozy-stack/pkg/consts" 12 "github.com/cozy/cozy-stack/pkg/couchdb" 13 "github.com/cozy/cozy-stack/pkg/logger" 14 "github.com/labstack/echo/v4" 15 ) 16 17 func createToken(c echo.Context) error { 18 domain := c.QueryParam("Domain") 19 audience := c.QueryParam("Audience") 20 scope := c.QueryParam("Scope") 21 subject := c.QueryParam("Subject") 22 in, err := lifecycle.GetInstance(domain) 23 if err != nil { 24 // With a cluster of couchdb, we can have a race condition where we 25 // query an index before it has been updated for an instance that has 26 // just been created. 27 // Cf https://issues.apache.org/jira/browse/COUCHDB-3336 28 time.Sleep(1 * time.Second) 29 in, err = lifecycle.GetInstance(domain) 30 if err != nil { 31 return wrapError(err) 32 } 33 } 34 issuedAt := time.Now() 35 validity := consts.DefaultValidityDuration 36 switch audience { 37 case consts.AppAudience, "webapp": 38 audience = consts.AppAudience 39 validity = consts.AppTokenValidityDuration 40 case consts.KonnectorAudience, "konnector": 41 audience = consts.KonnectorAudience 42 validity = consts.KonnectorTokenValidityDuration 43 case consts.AccessTokenAudience, "access-token": 44 if err := checkClient(in, subject); err != nil { 45 return err 46 } 47 audience = consts.AccessTokenAudience 48 validity = consts.AccessTokenValidityDuration 49 case consts.RefreshTokenAudience, "refresh-token": 50 if err := checkClient(in, subject); err != nil { 51 return err 52 } 53 audience = consts.RefreshTokenAudience 54 case consts.CLIAudience: 55 audience = consts.CLIAudience 56 validity = consts.CLITokenValidityDuration 57 default: 58 return echo.NewHTTPError(http.StatusBadRequest, "Unknown audience %s", audience) 59 } 60 if e := c.QueryParam("Expire"); e != "" && e != "0s" { 61 var d time.Duration 62 if d, err = time.ParseDuration(e); err == nil { 63 issuedAt = issuedAt.Add(d - validity) 64 } 65 } 66 token, err := in.MakeJWT(audience, subject, scope, "", issuedAt) 67 if err != nil { 68 return err 69 } 70 logger.WithDomain(domain).WithNamespace("loginaudit"). 71 Infof("%s token created from admin API at %s", audience, issuedAt) 72 return c.String(http.StatusOK, token) 73 } 74 75 func checkClient(inst *instance.Instance, clientID string) error { 76 client, err := oauth.FindClient(inst, clientID) 77 if err != nil { 78 return err 79 } 80 if client.Pending { 81 client.Pending = false 82 _ = couchdb.UpdateDoc(inst, client) 83 } 84 return nil 85 } 86 87 func registerClient(c echo.Context) error { 88 in, err := lifecycle.GetInstance(c.QueryParam("Domain")) 89 if err != nil { 90 return wrapError(err) 91 } 92 allowLoginScope, err := strconv.ParseBool(c.QueryParam("AllowLoginScope")) 93 if err != nil { 94 return wrapError(err) 95 } 96 97 client := oauth.Client{ 98 RedirectURIs: []string{c.QueryParam("RedirectURI")}, 99 ClientName: c.QueryParam("ClientName"), 100 SoftwareID: c.QueryParam("SoftwareID"), 101 AllowLoginScope: allowLoginScope, 102 } 103 if regErr := client.Create(in, oauth.NotPending); regErr != nil { 104 return c.String(http.StatusBadRequest, regErr.Description) 105 } 106 return c.JSON(http.StatusOK, client) 107 } 108 109 func findClientBySoftwareID(c echo.Context) error { 110 domain := c.QueryParam("domain") 111 softwareID := c.QueryParam("software_id") 112 113 inst, err := lifecycle.GetInstance(domain) 114 if err != nil { 115 return err 116 } 117 client, err := oauth.FindClientBySoftwareID(inst, softwareID) 118 if err != nil { 119 return err 120 } 121 return c.JSON(http.StatusOK, client) 122 }