Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: refresh tokens & refresh grant #1294

Merged
merged 1 commit into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions cmd/program/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,10 +321,6 @@ func RunMigrations(schema *proto.Schema, database db.Database) tea.Cmd {
Changes: m.Changes,
}

if !m.HasModelFieldChanges() {
return msg
}

err = m.Apply(context.Background())
if err != nil {
msg.Err = &ApplyMigrationsError{
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/dchest/uniuri v1.2.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/uniuri v1.2.0 h1:koIcOUdrTIivZgSLhHQvKgqdWZq5d7KdMEWF1Ud6+5g=
github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY=
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
Expand Down
2 changes: 1 addition & 1 deletion migrations/columns.sql
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_catalog.pg_index i on i.indexrelid = a.attrelid
WHERE
n.nspname = 'public'
AND c.relname not in ('keel_schema', 'pg_stat_statements_info', 'pg_stat_statements')
AND c.relname not in ('keel_schema', 'keel_refresh_token', 'pg_stat_statements_info', 'pg_stat_statements')
AND a.attnum > 0
AND NOT a.attisdropped
AND i.indexrelid is null; -- no indexes
5 changes: 4 additions & 1 deletion migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func (m *Migrations) Apply(ctx context.Context) error {
sql.WriteString(setUpdatedAt)
sql.WriteString("\n")

sql.WriteString("CREATE TABLE IF NOT EXISTS keel_schema ( schema TEXT NOT NULL );\n")
sql.WriteString("CREATE TABLE IF NOT EXISTS keel_schema (schema TEXT NOT NULL);\n")
sql.WriteString("DELETE FROM keel_schema;\n")

b, err := protojson.Marshal(m.Schema)
Expand All @@ -113,6 +113,9 @@ func (m *Migrations) Apply(ctx context.Context) error {
sql.WriteString(fmt.Sprintf("INSERT INTO keel_schema (schema) VALUES (%s);", escapedJSON))
sql.WriteString("\n")

sql.WriteString("CREATE TABLE IF NOT EXISTS keel_refresh_token (token TEXT NOT NULL PRIMARY KEY, identity_id TEXT NOT NULL, created_at TIMESTAMP, expires_at TIMESTAMP);\n")
sql.WriteString("\n")

sql.WriteString(fmt.Sprintf("SELECT set_trace_id('%s');\n", span.SpanContext().TraceID().String()))

sql.WriteString(m.SQL)
Expand Down
16 changes: 16 additions & 0 deletions runtime/apis/authapi/revoke_endpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package authapi

import (
"net/http"

"github.com/teamkeel/keel/proto"
"github.com/teamkeel/keel/runtime/common"
)

func RevokeHandler(schema *proto.Schema) common.HandlerFunc {
return func(r *http.Request) common.Response {
return common.Response{
Status: http.StatusNotImplemented,
}
}
}
64 changes: 60 additions & 4 deletions runtime/apis/authapi/token_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ const (
ArgSubjectToken = "subject_token"
ArgSubjectTokenType = "subject_token_type"
ArgRequestedTokeType = "requested_token_type"
ArgRefreshToken = "refresh_token"
)

const (
TokenType = "bearer"
)

// https://openid.net/specs/openid-connect-standard-1_0-21_orig.html#AccessTokenResponse
Expand Down Expand Up @@ -90,7 +95,50 @@ func TokenEndpointHandler(schema *proto.Schema) common.HandlerFunc {

switch grantType {
case GrantTypeRefreshToken:
return common.Response{Status: http.StatusNotImplemented}
if !r.Form.Has(ArgRefreshToken) {
return common.NewJsonResponse(http.StatusBadRequest, &TokenErrorResponse{
Error: TokenEndpointInvalidRequest,
ErrorDescription: "the refresh token must be provided in the refresh_token field",
}, nil)
}

refreshTokenRaw := r.Form.Get(ArgRefreshToken)

if refreshTokenRaw == "" {
return common.NewJsonResponse(http.StatusBadRequest, &TokenErrorResponse{
Error: TokenEndpointInvalidRequest,
ErrorDescription: "the refresh token in the refresh_token field cannot be an empty string",
}, nil)
}

isValid, newRefreshToken, identityId, err := oauth.RotateRefreshToken(ctx, refreshTokenRaw)
if err != nil {
span.RecordError(err)
return common.NewJsonResponse(http.StatusInternalServerError, nil, nil)
}

if !isValid {
return common.NewJsonResponse(http.StatusUnauthorized, &TokenErrorResponse{
Error: TokenEndpointInvalidClient,
ErrorDescription: "possible causes may be that the refresh token has been revoked or has expired",
}, nil)
}

// Generate an access token for this identity.
accessTokenRaw, expiresIn, err := oauth.GenerateAccessToken(ctx, identityId)
if err != nil {
span.RecordError(err)
return common.NewJsonResponse(http.StatusInternalServerError, nil, nil)
}

response := &TokenResponse{
AccessToken: accessTokenRaw,
TokenType: TokenType,
ExpiresIn: int(expiresIn.Seconds()),
RefreshToken: newRefreshToken,
}

return common.NewJsonResponse(http.StatusOK, response, nil)
case GrantTypeTokenExchange:
if !r.Form.Has(ArgSubjectToken) {
return common.NewJsonResponse(http.StatusBadRequest, &TokenErrorResponse{
Expand Down Expand Up @@ -176,10 +224,18 @@ func TokenEndpointHandler(schema *proto.Schema) common.HandlerFunc {
return common.NewJsonResponse(http.StatusInternalServerError, nil, nil)
}

// Generate a refresh token.
refreshTokenRaw, err := oauth.NewRefreshToken(ctx, identity.Id)
if err != nil {
span.RecordError(err)
return common.NewJsonResponse(http.StatusInternalServerError, nil, nil)
}

response := &TokenResponse{
AccessToken: accessTokenRaw,
TokenType: "bearer",
ExpiresIn: int(expiresIn.Seconds()),
AccessToken: accessTokenRaw,
TokenType: TokenType,
ExpiresIn: int(expiresIn.Seconds()),
RefreshToken: refreshTokenRaw,
}

return common.NewJsonResponse(http.StatusOK, response, nil)
Expand Down
Loading
Loading