package codersdk import ( "context" "encoding/json" "fmt" "net/http" "time" "github.com/google/uuid" "golang.org/x/xerrors" ) const ( LicenseExpiryClaim = "license_expires" ) type AddLicenseRequest struct { License string `json:"license" validate:"required"` } type License struct { ID int32 `json:"id"` UUID uuid.UUID `json:"uuid" format:"uuid"` UploadedAt time.Time `json:"uploaded_at" format:"date-time"` // Claims are the JWT claims asserted by the license. Here we use // a generic string map to ensure that all data from the server is // parsed verbatim, not just the fields this version of Coder // understands. Claims map[string]interface{} `json:"claims" table:"claims"` } // ExpiresAt returns the expiration time of the license. // If the claim is missing or has an unexpected type, an error is returned. func (l *License) ExpiresAt() (time.Time, error) { expClaim, ok := l.Claims[LicenseExpiryClaim] if !ok { return time.Time{}, xerrors.New("license_expires claim is missing") } // This claim should be a unix timestamp. // Everything is already an interface{}, so we need to do some type // assertions to figure out what we're dealing with. if unix, ok := expClaim.(json.Number); ok { i64, err := unix.Int64() if err != nil { return time.Time{}, xerrors.Errorf("license_expires claim is not a valid unix timestamp: %w", err) } return time.Unix(i64, 0), nil } return time.Time{}, xerrors.Errorf("license_expires claim has unexpected type %T", expClaim) } func (l *License) Trial() bool { if trail, ok := l.Claims["trail"].(bool); ok { return trail } return false } func (l *License) AllFeaturesClaim() bool { if all, ok := l.Claims["all_features"].(bool); ok { return all } return false } // FeaturesClaims provides the feature claims in license. // This only returns the explicit claims. If checking for actual usage, // also check `AllFeaturesClaim`. func (l *License) FeaturesClaims() (map[FeatureName]int64, error) { strMap, ok := l.Claims["features"].(map[string]interface{}) if !ok { return nil, xerrors.New("features key is unexpected type") } fMap := make(map[FeatureName]int64) for k, v := range strMap { jn, ok := v.(json.Number) if !ok { return nil, xerrors.Errorf("feature %q has unexpected type", k) } n, err := jn.Int64() if err != nil { return nil, err } fMap[FeatureName(k)] = n } return fMap, nil } func (c *Client) AddLicense(ctx context.Context, r AddLicenseRequest) (License, error) { res, err := c.Request(ctx, http.MethodPost, "/api/v2/licenses", r) if err != nil { return License{}, err } defer res.Body.Close() if res.StatusCode != http.StatusCreated { return License{}, ReadBodyAsError(res) } var l License d := json.NewDecoder(res.Body) d.UseNumber() return l, d.Decode(&l) } func (c *Client) Licenses(ctx context.Context) ([]License, error) { res, err := c.Request(ctx, http.MethodGet, "/api/v2/licenses", nil) if err != nil { return nil, err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return nil, ReadBodyAsError(res) } var licenses []License d := json.NewDecoder(res.Body) d.UseNumber() return licenses, d.Decode(&licenses) } func (c *Client) DeleteLicense(ctx context.Context, id int32) error { res, err := c.Request(ctx, http.MethodDelete, fmt.Sprintf("/api/v2/licenses/%d", id), nil) if err != nil { return err } defer res.Body.Close() if res.StatusCode != http.StatusOK { return ReadBodyAsError(res) } return nil }