feat: Add GitHub OAuth (#1050)

* Initial oauth

* Add Github authentication

* Add AuthMethods endpoint

* Add frontend

* Rename basic authentication to password

* Add flags for configuring GitHub auth

* Remove name from API keys

* Fix authmethods in test

* Add stories and display auth methods error
This commit is contained in:
Kyle Carberry
2022-04-23 17:58:57 -05:00
committed by GitHub
parent 3976994781
commit 7496c3da81
41 changed files with 1251 additions and 422 deletions

View File

@ -434,6 +434,16 @@ func (q *fakeQuerier) GetWorkspacesByUserID(_ context.Context, req database.GetW
return workspaces, nil
}
func (q *fakeQuerier) GetOrganizations(_ context.Context) ([]database.Organization, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
if len(q.organizations) == 0 {
return nil, sql.ErrNoRows
}
return q.organizations, nil
}
func (q *fakeQuerier) GetOrganizationByID(_ context.Context, id uuid.UUID) (database.Organization, error) {
q.mutex.RLock()
defer q.mutex.RUnlock()
@ -856,21 +866,18 @@ func (q *fakeQuerier) InsertAPIKey(_ context.Context, arg database.InsertAPIKeyP
//nolint:gosimple
key := database.APIKey{
ID: arg.ID,
HashedSecret: arg.HashedSecret,
UserID: arg.UserID,
Application: arg.Application,
Name: arg.Name,
LastUsed: arg.LastUsed,
ExpiresAt: arg.ExpiresAt,
CreatedAt: arg.CreatedAt,
UpdatedAt: arg.UpdatedAt,
LoginType: arg.LoginType,
OIDCAccessToken: arg.OIDCAccessToken,
OIDCRefreshToken: arg.OIDCRefreshToken,
OIDCIDToken: arg.OIDCIDToken,
OIDCExpiry: arg.OIDCExpiry,
DevurlToken: arg.DevurlToken,
ID: arg.ID,
HashedSecret: arg.HashedSecret,
UserID: arg.UserID,
ExpiresAt: arg.ExpiresAt,
CreatedAt: arg.CreatedAt,
UpdatedAt: arg.UpdatedAt,
LastUsed: arg.LastUsed,
LoginType: arg.LoginType,
OAuthAccessToken: arg.OAuthAccessToken,
OAuthRefreshToken: arg.OAuthRefreshToken,
OAuthIDToken: arg.OAuthIDToken,
OAuthExpiry: arg.OAuthExpiry,
}
q.apiKeys = append(q.apiKeys, key)
return key, nil
@ -1185,9 +1192,9 @@ func (q *fakeQuerier) UpdateAPIKeyByID(_ context.Context, arg database.UpdateAPI
}
apiKey.LastUsed = arg.LastUsed
apiKey.ExpiresAt = arg.ExpiresAt
apiKey.OIDCAccessToken = arg.OIDCAccessToken
apiKey.OIDCRefreshToken = arg.OIDCRefreshToken
apiKey.OIDCExpiry = arg.OIDCExpiry
apiKey.OAuthAccessToken = arg.OAuthAccessToken
apiKey.OAuthRefreshToken = arg.OAuthRefreshToken
apiKey.OAuthExpiry = arg.OAuthExpiry
q.apiKeys[index] = apiKey
return nil
}

View File

@ -14,9 +14,8 @@ CREATE TYPE log_source AS ENUM (
);
CREATE TYPE login_type AS ENUM (
'built-in',
'saml',
'oidc'
'password',
'github'
);
CREATE TYPE parameter_destination_scheme AS ENUM (
@ -67,18 +66,15 @@ CREATE TABLE api_keys (
id text NOT NULL,
hashed_secret bytea NOT NULL,
user_id uuid NOT NULL,
application boolean NOT NULL,
name text NOT NULL,
last_used timestamp with time zone NOT NULL,
expires_at timestamp with time zone NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
login_type login_type NOT NULL,
oidc_access_token text DEFAULT ''::text NOT NULL,
oidc_refresh_token text DEFAULT ''::text NOT NULL,
oidc_id_token text DEFAULT ''::text NOT NULL,
oidc_expiry timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL,
devurl_token boolean DEFAULT false NOT NULL
oauth_access_token text DEFAULT ''::text NOT NULL,
oauth_refresh_token text DEFAULT ''::text NOT NULL,
oauth_id_token text DEFAULT ''::text NOT NULL,
oauth_expiry timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL
);
CREATE TABLE files (

View File

@ -4,14 +4,9 @@
-- All tables and types are stolen from:
-- https://github.com/coder/m/blob/47b6fc383347b9f9fab424d829c482defd3e1fe2/product/coder/pkg/database/dump.sql
--
-- Name: users; Type: TABLE; Schema: public; Owner: coder
--
CREATE TYPE login_type AS ENUM (
'built-in',
'saml',
'oidc'
'password',
'github'
);
CREATE TABLE IF NOT EXISTS users (
@ -31,10 +26,6 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email ON users USING btree (email);
CREATE UNIQUE INDEX IF NOT EXISTS idx_users_username ON users USING btree (username);
CREATE UNIQUE INDEX IF NOT EXISTS users_username_lower_idx ON users USING btree (lower(username));
--
-- Name: organizations; Type: TABLE; Schema: Owner: coder
--
CREATE TABLE IF NOT EXISTS organizations (
id uuid NOT NULL,
name text NOT NULL,
@ -68,18 +59,15 @@ CREATE TABLE IF NOT EXISTS api_keys (
id text NOT NULL,
hashed_secret bytea NOT NULL,
user_id uuid NOT NULL,
application boolean NOT NULL,
name text NOT NULL,
last_used timestamp with time zone NOT NULL,
expires_at timestamp with time zone NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
login_type login_type NOT NULL,
oidc_access_token text DEFAULT ''::text NOT NULL,
oidc_refresh_token text DEFAULT ''::text NOT NULL,
oidc_id_token text DEFAULT ''::text NOT NULL,
oidc_expiry timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL,
devurl_token boolean DEFAULT false NOT NULL,
oauth_access_token text DEFAULT ''::text NOT NULL,
oauth_refresh_token text DEFAULT ''::text NOT NULL,
oauth_id_token text DEFAULT ''::text NOT NULL,
oauth_expiry timestamp with time zone DEFAULT '0001-01-01 00:00:00+00'::timestamp with time zone NOT NULL,
PRIMARY KEY (id)
);

View File

@ -56,9 +56,8 @@ func (e *LogSource) Scan(src interface{}) error {
type LoginType string
const (
LoginTypeBuiltIn LoginType = "built-in"
LoginTypeSaml LoginType = "saml"
LoginTypeOIDC LoginType = "oidc"
LoginTypePassword LoginType = "password"
LoginTypeGithub LoginType = "github"
)
func (e *LoginType) Scan(src interface{}) error {
@ -230,21 +229,18 @@ func (e *WorkspaceTransition) Scan(src interface{}) error {
}
type APIKey struct {
ID string `db:"id" json:"id"`
HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"`
UserID uuid.UUID `db:"user_id" json:"user_id"`
Application bool `db:"application" json:"application"`
Name string `db:"name" json:"name"`
LastUsed time.Time `db:"last_used" json:"last_used"`
ExpiresAt time.Time `db:"expires_at" json:"expires_at"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
LoginType LoginType `db:"login_type" json:"login_type"`
OIDCAccessToken string `db:"oidc_access_token" json:"oidc_access_token"`
OIDCRefreshToken string `db:"oidc_refresh_token" json:"oidc_refresh_token"`
OIDCIDToken string `db:"oidc_id_token" json:"oidc_id_token"`
OIDCExpiry time.Time `db:"oidc_expiry" json:"oidc_expiry"`
DevurlToken bool `db:"devurl_token" json:"devurl_token"`
ID string `db:"id" json:"id"`
HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"`
UserID uuid.UUID `db:"user_id" json:"user_id"`
LastUsed time.Time `db:"last_used" json:"last_used"`
ExpiresAt time.Time `db:"expires_at" json:"expires_at"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
LoginType LoginType `db:"login_type" json:"login_type"`
OAuthAccessToken string `db:"oauth_access_token" json:"oauth_access_token"`
OAuthRefreshToken string `db:"oauth_refresh_token" json:"oauth_refresh_token"`
OAuthIDToken string `db:"oauth_id_token" json:"oauth_id_token"`
OAuthExpiry time.Time `db:"oauth_expiry" json:"oauth_expiry"`
}
type File struct {

View File

@ -18,6 +18,7 @@ type querier interface {
GetOrganizationByID(ctx context.Context, id uuid.UUID) (Organization, error)
GetOrganizationByName(ctx context.Context, name string) (Organization, error)
GetOrganizationMemberByUserID(ctx context.Context, arg GetOrganizationMemberByUserIDParams) (OrganizationMember, error)
GetOrganizations(ctx context.Context) ([]Organization, error)
GetOrganizationsByUserID(ctx context.Context, userID uuid.UUID) ([]Organization, error)
GetParameterSchemasByJobID(ctx context.Context, jobID uuid.UUID) ([]ParameterSchema, error)
GetParameterValueByScopeAndName(ctx context.Context, arg GetParameterValueByScopeAndNameParams) (ParameterValue, error)

View File

@ -15,7 +15,7 @@ import (
const getAPIKeyByID = `-- name: GetAPIKeyByID :one
SELECT
id, hashed_secret, user_id, application, name, last_used, expires_at, created_at, updated_at, login_type, oidc_access_token, oidc_refresh_token, oidc_id_token, oidc_expiry, devurl_token
id, hashed_secret, user_id, last_used, expires_at, created_at, updated_at, login_type, oauth_access_token, oauth_refresh_token, oauth_id_token, oauth_expiry
FROM
api_keys
WHERE
@ -31,18 +31,15 @@ func (q *sqlQuerier) GetAPIKeyByID(ctx context.Context, id string) (APIKey, erro
&i.ID,
&i.HashedSecret,
&i.UserID,
&i.Application,
&i.Name,
&i.LastUsed,
&i.ExpiresAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.LoginType,
&i.OIDCAccessToken,
&i.OIDCRefreshToken,
&i.OIDCIDToken,
&i.OIDCExpiry,
&i.DevurlToken,
&i.OAuthAccessToken,
&i.OAuthRefreshToken,
&i.OAuthIDToken,
&i.OAuthExpiry,
)
return i, err
}
@ -53,55 +50,33 @@ INSERT INTO
id,
hashed_secret,
user_id,
application,
"name",
last_used,
expires_at,
created_at,
updated_at,
login_type,
oidc_access_token,
oidc_refresh_token,
oidc_id_token,
oidc_expiry,
devurl_token
oauth_access_token,
oauth_refresh_token,
oauth_id_token,
oauth_expiry
)
VALUES
(
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10,
$11,
$12,
$13,
$14,
$15
) RETURNING id, hashed_secret, user_id, application, name, last_used, expires_at, created_at, updated_at, login_type, oidc_access_token, oidc_refresh_token, oidc_id_token, oidc_expiry, devurl_token
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING id, hashed_secret, user_id, last_used, expires_at, created_at, updated_at, login_type, oauth_access_token, oauth_refresh_token, oauth_id_token, oauth_expiry
`
type InsertAPIKeyParams struct {
ID string `db:"id" json:"id"`
HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"`
UserID uuid.UUID `db:"user_id" json:"user_id"`
Application bool `db:"application" json:"application"`
Name string `db:"name" json:"name"`
LastUsed time.Time `db:"last_used" json:"last_used"`
ExpiresAt time.Time `db:"expires_at" json:"expires_at"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
LoginType LoginType `db:"login_type" json:"login_type"`
OIDCAccessToken string `db:"oidc_access_token" json:"oidc_access_token"`
OIDCRefreshToken string `db:"oidc_refresh_token" json:"oidc_refresh_token"`
OIDCIDToken string `db:"oidc_id_token" json:"oidc_id_token"`
OIDCExpiry time.Time `db:"oidc_expiry" json:"oidc_expiry"`
DevurlToken bool `db:"devurl_token" json:"devurl_token"`
ID string `db:"id" json:"id"`
HashedSecret []byte `db:"hashed_secret" json:"hashed_secret"`
UserID uuid.UUID `db:"user_id" json:"user_id"`
LastUsed time.Time `db:"last_used" json:"last_used"`
ExpiresAt time.Time `db:"expires_at" json:"expires_at"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
LoginType LoginType `db:"login_type" json:"login_type"`
OAuthAccessToken string `db:"oauth_access_token" json:"oauth_access_token"`
OAuthRefreshToken string `db:"oauth_refresh_token" json:"oauth_refresh_token"`
OAuthIDToken string `db:"oauth_id_token" json:"oauth_id_token"`
OAuthExpiry time.Time `db:"oauth_expiry" json:"oauth_expiry"`
}
func (q *sqlQuerier) InsertAPIKey(ctx context.Context, arg InsertAPIKeyParams) (APIKey, error) {
@ -109,36 +84,30 @@ func (q *sqlQuerier) InsertAPIKey(ctx context.Context, arg InsertAPIKeyParams) (
arg.ID,
arg.HashedSecret,
arg.UserID,
arg.Application,
arg.Name,
arg.LastUsed,
arg.ExpiresAt,
arg.CreatedAt,
arg.UpdatedAt,
arg.LoginType,
arg.OIDCAccessToken,
arg.OIDCRefreshToken,
arg.OIDCIDToken,
arg.OIDCExpiry,
arg.DevurlToken,
arg.OAuthAccessToken,
arg.OAuthRefreshToken,
arg.OAuthIDToken,
arg.OAuthExpiry,
)
var i APIKey
err := row.Scan(
&i.ID,
&i.HashedSecret,
&i.UserID,
&i.Application,
&i.Name,
&i.LastUsed,
&i.ExpiresAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.LoginType,
&i.OIDCAccessToken,
&i.OIDCRefreshToken,
&i.OIDCIDToken,
&i.OIDCExpiry,
&i.DevurlToken,
&i.OAuthAccessToken,
&i.OAuthRefreshToken,
&i.OAuthIDToken,
&i.OAuthExpiry,
)
return i, err
}
@ -149,20 +118,20 @@ UPDATE
SET
last_used = $2,
expires_at = $3,
oidc_access_token = $4,
oidc_refresh_token = $5,
oidc_expiry = $6
oauth_access_token = $4,
oauth_refresh_token = $5,
oauth_expiry = $6
WHERE
id = $1
`
type UpdateAPIKeyByIDParams struct {
ID string `db:"id" json:"id"`
LastUsed time.Time `db:"last_used" json:"last_used"`
ExpiresAt time.Time `db:"expires_at" json:"expires_at"`
OIDCAccessToken string `db:"oidc_access_token" json:"oidc_access_token"`
OIDCRefreshToken string `db:"oidc_refresh_token" json:"oidc_refresh_token"`
OIDCExpiry time.Time `db:"oidc_expiry" json:"oidc_expiry"`
ID string `db:"id" json:"id"`
LastUsed time.Time `db:"last_used" json:"last_used"`
ExpiresAt time.Time `db:"expires_at" json:"expires_at"`
OAuthAccessToken string `db:"oauth_access_token" json:"oauth_access_token"`
OAuthRefreshToken string `db:"oauth_refresh_token" json:"oauth_refresh_token"`
OAuthExpiry time.Time `db:"oauth_expiry" json:"oauth_expiry"`
}
func (q *sqlQuerier) UpdateAPIKeyByID(ctx context.Context, arg UpdateAPIKeyByIDParams) error {
@ -170,9 +139,9 @@ func (q *sqlQuerier) UpdateAPIKeyByID(ctx context.Context, arg UpdateAPIKeyByIDP
arg.ID,
arg.LastUsed,
arg.ExpiresAt,
arg.OIDCAccessToken,
arg.OIDCRefreshToken,
arg.OIDCExpiry,
arg.OAuthAccessToken,
arg.OAuthRefreshToken,
arg.OAuthExpiry,
)
return err
}
@ -453,6 +422,42 @@ func (q *sqlQuerier) GetOrganizationByName(ctx context.Context, name string) (Or
return i, err
}
const getOrganizations = `-- name: GetOrganizations :many
SELECT
id, name, description, created_at, updated_at
FROM
organizations
`
func (q *sqlQuerier) GetOrganizations(ctx context.Context) ([]Organization, error) {
rows, err := q.db.QueryContext(ctx, getOrganizations)
if err != nil {
return nil, err
}
defer rows.Close()
var items []Organization
for rows.Next() {
var i Organization
if err := rows.Scan(
&i.ID,
&i.Name,
&i.Description,
&i.CreatedAt,
&i.UpdatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getOrganizationsByUserID = `-- name: GetOrganizationsByUserID :many
SELECT
id, name, description, created_at, updated_at

View File

@ -14,37 +14,18 @@ INSERT INTO
id,
hashed_secret,
user_id,
application,
"name",
last_used,
expires_at,
created_at,
updated_at,
login_type,
oidc_access_token,
oidc_refresh_token,
oidc_id_token,
oidc_expiry,
devurl_token
oauth_access_token,
oauth_refresh_token,
oauth_id_token,
oauth_expiry
)
VALUES
(
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10,
$11,
$12,
$13,
$14,
$15
) RETURNING *;
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *;
-- name: UpdateAPIKeyByID :exec
UPDATE
@ -52,8 +33,8 @@ UPDATE
SET
last_used = $2,
expires_at = $3,
oidc_access_token = $4,
oidc_refresh_token = $5,
oidc_expiry = $6
oauth_access_token = $4,
oauth_refresh_token = $5,
oauth_expiry = $6
WHERE
id = $1;

View File

@ -1,3 +1,9 @@
-- name: GetOrganizations :many
SELECT
*
FROM
organizations;
-- name: GetOrganizationByID :one
SELECT
*

View File

@ -21,10 +21,10 @@ overrides:
rename:
api_key: APIKey
login_type_oidc: LoginTypeOIDC
oidc_access_token: OIDCAccessToken
oidc_expiry: OIDCExpiry
oidc_id_token: OIDCIDToken
oidc_refresh_token: OIDCRefreshToken
oauth_access_token: OAuthAccessToken
oauth_expiry: OAuthExpiry
oauth_id_token: OAuthIDToken
oauth_refresh_token: OAuthRefreshToken
parameter_type_system_hcl: ParameterTypeSystemHCL
userstatus: UserStatus
gitsshkey: GitSSHKey