@@ -2,27 +2,24 @@ package coderd
22
33import (
44"context"
5- "crypto/sha256"
65"fmt"
7- "net"
86"net/http"
97"strconv"
108"time"
119
1210"github.com/go-chi/chi/v5"
1311"github.com/google/uuid"
1412"github.com/moby/moby/pkg/namesgenerator"
15- "github.com/tabbed/pqtype"
1613"golang.org/x/xerrors"
1714
15+ "github.com/coder/coder/coderd/apikey"
1816"github.com/coder/coder/coderd/audit"
1917"github.com/coder/coder/coderd/database"
2018"github.com/coder/coder/coderd/httpapi"
2119"github.com/coder/coder/coderd/httpmw"
2220"github.com/coder/coder/coderd/rbac"
2321"github.com/coder/coder/coderd/telemetry"
2422"github.com/coder/coder/codersdk"
25- "github.com/coder/coder/cryptorand"
2623)
2724
2825// Creates a new token API key that effectively doesn't expire.
@@ -83,13 +80,14 @@ func (api *API) postToken(rw http.ResponseWriter, r *http.Request) {
8380return
8481}
8582
86- cookie ,key ,err := api .createAPIKey (ctx ,createAPIKeyParams {
87- UserID :user .ID ,
88- LoginType :database .LoginTypeToken ,
89- ExpiresAt :database .Now ().Add (lifeTime ),
90- Scope :scope ,
91- LifetimeSeconds :int64 (lifeTime .Seconds ()),
92- TokenName :tokenName ,
83+ cookie ,key ,err := api .createAPIKey (ctx , apikey.CreateParams {
84+ UserID :user .ID ,
85+ LoginType :database .LoginTypeToken ,
86+ DeploymentValues :api .DeploymentValues ,
87+ ExpiresAt :database .Now ().Add (lifeTime ),
88+ Scope :scope ,
89+ LifetimeSeconds :int64 (lifeTime .Seconds ()),
90+ TokenName :tokenName ,
9391})
9492if err != nil {
9593if database .IsUniqueViolation (err ,database .UniqueIndexApiKeyName ) {
@@ -127,10 +125,11 @@ func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) {
127125user := httpmw .UserParam (r )
128126
129127lifeTime := time .Hour * 24 * 7
130- cookie ,_ ,err := api .createAPIKey (ctx ,createAPIKeyParams {
131- UserID :user .ID ,
132- LoginType :database .LoginTypePassword ,
133- RemoteAddr :r .RemoteAddr ,
128+ cookie ,_ ,err := api .createAPIKey (ctx , apikey.CreateParams {
129+ UserID :user .ID ,
130+ DeploymentValues :api .DeploymentValues ,
131+ LoginType :database .LoginTypePassword ,
132+ RemoteAddr :r .RemoteAddr ,
134133// All api generated keys will last 1 week. Browser login tokens have
135134// a shorter life.
136135ExpiresAt :database .Now ().Add (lifeTime ),
@@ -359,33 +358,6 @@ func (api *API) tokenConfig(rw http.ResponseWriter, r *http.Request) {
359358)
360359}
361360
362- // Generates a new ID and secret for an API key.
363- func GenerateAPIKeyIDSecret () (id string ,secret string ,err error ) {
364- // Length of an API Key ID.
365- id ,err = cryptorand .String (10 )
366- if err != nil {
367- return "" ,"" ,err
368- }
369- // Length of an API Key secret.
370- secret ,err = cryptorand .String (22 )
371- if err != nil {
372- return "" ,"" ,err
373- }
374- return id ,secret ,nil
375- }
376-
377- type createAPIKeyParams struct {
378- UserID uuid.UUID
379- RemoteAddr string
380- LoginType database.LoginType
381-
382- // Optional.
383- ExpiresAt time.Time
384- LifetimeSeconds int64
385- Scope database.APIKeyScope
386- TokenName string
387- }
388-
389361func (api * API )validateAPIKeyLifetime (lifetime time.Duration )error {
390362if lifetime <= 0 {
391363return xerrors .New ("lifetime must be positive number greater than 0" )
@@ -401,79 +373,27 @@ func (api *API) validateAPIKeyLifetime(lifetime time.Duration) error {
401373return nil
402374}
403375
404- func (api * API )createAPIKey (ctx context.Context ,params createAPIKeyParams ) (* http.Cookie ,* database.APIKey ,error ) {
405- keyID , keySecret ,err := GenerateAPIKeyIDSecret ( )
376+ func (api * API )createAPIKey (ctx context.Context ,params apikey. CreateParams ) (* http.Cookie ,* database.APIKey ,error ) {
377+ key , sessionToken ,err := apikey . Generate ( params )
406378if err != nil {
407379return nil ,nil ,xerrors .Errorf ("generate API key: %w" ,err )
408380}
409- hashed := sha256 .Sum256 ([]byte (keySecret ))
410-
411- // Default expires at to now+lifetime, or use the configured value if not
412- // set.
413- if params .ExpiresAt .IsZero () {
414- if params .LifetimeSeconds != 0 {
415- params .ExpiresAt = database .Now ().Add (time .Duration (params .LifetimeSeconds )* time .Second )
416- }else {
417- params .ExpiresAt = database .Now ().Add (api .DeploymentValues .SessionDuration .Value ())
418- params .LifetimeSeconds = int64 (api .DeploymentValues .SessionDuration .Value ().Seconds ())
419- }
420- }
421- if params .LifetimeSeconds == 0 {
422- params .LifetimeSeconds = int64 (time .Until (params .ExpiresAt ).Seconds ())
423- }
424-
425- ip := net .ParseIP (params .RemoteAddr )
426- if ip == nil {
427- ip = net .IPv4 (0 ,0 ,0 ,0 )
428- }
429- bitlen := len (ip )* 8
430381
431- scope := database .APIKeyScopeAll
432- if params .Scope != "" {
433- scope = params .Scope
434- }
435- switch scope {
436- case database .APIKeyScopeAll ,database .APIKeyScopeApplicationConnect :
437- default :
438- return nil ,nil ,xerrors .Errorf ("invalid API key scope: %q" ,scope )
439- }
440-
441- key ,err := api .Database .InsertAPIKey (ctx , database.InsertAPIKeyParams {
442- ID :keyID ,
443- UserID :params .UserID ,
444- LifetimeSeconds :params .LifetimeSeconds ,
445- IPAddress : pqtype.Inet {
446- IPNet : net.IPNet {
447- IP :ip ,
448- Mask :net .CIDRMask (bitlen ,bitlen ),
449- },
450- Valid :true ,
451- },
452- // Make sure in UTC time for common time zone
453- ExpiresAt :params .ExpiresAt .UTC (),
454- CreatedAt :database .Now (),
455- UpdatedAt :database .Now (),
456- HashedSecret :hashed [:],
457- LoginType :params .LoginType ,
458- Scope :scope ,
459- TokenName :params .TokenName ,
460- })
382+ newkey ,err := api .Database .InsertAPIKey (ctx ,key )
461383if err != nil {
462384return nil ,nil ,xerrors .Errorf ("insert API key: %w" ,err )
463385}
464386
465387api .Telemetry .Report (& telemetry.Snapshot {
466- APIKeys : []telemetry.APIKey {telemetry .ConvertAPIKey (key )},
388+ APIKeys : []telemetry.APIKey {telemetry .ConvertAPIKey (newkey )},
467389})
468390
469- // This format is consumed by the APIKey middleware.
470- sessionToken := fmt .Sprintf ("%s-%s" ,keyID ,keySecret )
471391return & http.Cookie {
472392Name :codersdk .SessionTokenCookie ,
473393Value :sessionToken ,
474394Path :"/" ,
475395HttpOnly :true ,
476396SameSite :http .SameSiteLaxMode ,
477397Secure :api .SecureAuthCookie ,
478- },& key ,nil
398+ },& newkey ,nil
479399}