@@ -2,27 +2,24 @@ package coderd
2
2
3
3
import (
4
4
"context"
5
- "crypto/sha256"
6
5
"fmt"
7
- "net"
8
6
"net/http"
9
7
"strconv"
10
8
"time"
11
9
12
10
"github.com/go-chi/chi/v5"
13
11
"github.com/google/uuid"
14
12
"github.com/moby/moby/pkg/namesgenerator"
15
- "github.com/tabbed/pqtype"
16
13
"golang.org/x/xerrors"
17
14
15
+ "github.com/coder/coder/coderd/apikey"
18
16
"github.com/coder/coder/coderd/audit"
19
17
"github.com/coder/coder/coderd/database"
20
18
"github.com/coder/coder/coderd/httpapi"
21
19
"github.com/coder/coder/coderd/httpmw"
22
20
"github.com/coder/coder/coderd/rbac"
23
21
"github.com/coder/coder/coderd/telemetry"
24
22
"github.com/coder/coder/codersdk"
25
- "github.com/coder/coder/cryptorand"
26
23
)
27
24
28
25
// 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) {
83
80
return
84
81
}
85
82
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 ,
93
91
})
94
92
if err != nil {
95
93
if database .IsUniqueViolation (err ,database .UniqueIndexApiKeyName ) {
@@ -127,10 +125,11 @@ func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) {
127
125
user := httpmw .UserParam (r )
128
126
129
127
lifeTime := 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 ,
134
133
// All api generated keys will last 1 week. Browser login tokens have
135
134
// a shorter life.
136
135
ExpiresAt :database .Now ().Add (lifeTime ),
@@ -359,33 +358,6 @@ func (api *API) tokenConfig(rw http.ResponseWriter, r *http.Request) {
359
358
)
360
359
}
361
360
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
-
389
361
func (api * API )validateAPIKeyLifetime (lifetime time.Duration )error {
390
362
if lifetime <= 0 {
391
363
return xerrors .New ("lifetime must be positive number greater than 0" )
@@ -401,79 +373,27 @@ func (api *API) validateAPIKeyLifetime(lifetime time.Duration) error {
401
373
return nil
402
374
}
403
375
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 )
406
378
if err != nil {
407
379
return nil ,nil ,xerrors .Errorf ("generate API key: %w" ,err )
408
380
}
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
430
381
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 )
461
383
if err != nil {
462
384
return nil ,nil ,xerrors .Errorf ("insert API key: %w" ,err )
463
385
}
464
386
465
387
api .Telemetry .Report (& telemetry.Snapshot {
466
- APIKeys : []telemetry.APIKey {telemetry .ConvertAPIKey (key )},
388
+ APIKeys : []telemetry.APIKey {telemetry .ConvertAPIKey (newkey )},
467
389
})
468
390
469
- // This format is consumed by the APIKey middleware.
470
- sessionToken := fmt .Sprintf ("%s-%s" ,keyID ,keySecret )
471
391
return & http.Cookie {
472
392
Name :codersdk .SessionTokenCookie ,
473
393
Value :sessionToken ,
474
394
Path :"/" ,
475
395
HttpOnly :true ,
476
396
SameSite :http .SameSiteLaxMode ,
477
397
Secure :api .SecureAuthCookie ,
478
- },& key ,nil
398
+ },& newkey ,nil
479
399
}