Skip to content

Commit

Permalink
feature: RS256 JWT signature
Browse files Browse the repository at this point in the history
* feature: First user as superadmin
* enhancement: Add Kaigara to the Dockerfile
* enhancement: Add .drone.yml w/ Docker build
  • Loading branch information
Danylo Patsora authored and dpatsora committed Jan 20, 2023
1 parent 6581728 commit 1315789
Show file tree
Hide file tree
Showing 37 changed files with 343 additions and 102 deletions.
26 changes: 26 additions & 0 deletions .drone.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
type: docker
kind: pipeline
name: "Main"

steps:
- name: Docker build Git SHA
image: plugins/docker:20
pull: if-not-exists
environment:
DOCKER_BUILDKIT: 1
settings:
username:
from_secret: quay_username
password:
from_secret: quay_password
repo: quay.io/openware/gotrue
registry: quay.io
tag: ${DRONE_COMMIT:0:7}
purge: false
when:
event:
- push
branch:
- stable/ow
- feature/asymmetric-auth
7 changes: 6 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ENV GO111MODULE=on
ENV CGO_ENABLED=0
ENV GOOS=linux

RUN apk add --no-cache make git
RUN apk add --no-cache make git curl

WORKDIR /go/src/github.com/netlify/gotrue

Expand All @@ -15,11 +15,16 @@ RUN make deps
COPY . /go/src/github.com/netlify/gotrue
RUN make build

ARG KAIGARA_VERSION=v1.0.10
RUN curl -Lo ./kaigara https://github.com/openware/kaigara/releases/download/${KAIGARA_VERSION}/kaigara \
&& chmod +x ./kaigara

FROM alpine:3.17
RUN adduser -D -u 1000 netlify

RUN apk add --no-cache ca-certificates
COPY --from=build /go/src/github.com/netlify/gotrue/gotrue /usr/local/bin/gotrue
COPY --from=build /go/src/github.com/netlify/gotrue/kaigara /usr/local/bin/kaigara
COPY --from=build /go/src/github.com/netlify/gotrue/migrations /usr/local/etc/gotrue/migrations/

ENV GOTRUE_DB_MIGRATIONS_PATH /usr/local/etc/gotrue/migrations
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,13 +395,17 @@ by default.

```properties
GOTRUE_JWT_SECRET=supersecretvalue
GOTRUE_JWT_ALGORITHM=RS256
GOTRUE_JWT_EXP=3600
GOTRUE_JWT_AUD=netlify
```
`JWT_ALGORITHM` - `string`

The signing algorithm for the JWT. Defaults to HS256.

`JWT_SECRET` - `string` **required**

The secret used to sign JWT tokens with.
The secret used to sign JWT tokens with. If signing alogrithm is RS256, secret has to be Base64 encoded RSA private key.

`JWT_EXP` - `number`

Expand Down
5 changes: 3 additions & 2 deletions api/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import (
"time"

jwt "github.com/golang-jwt/jwt"
"github.com/netlify/gotrue/conf"
"github.com/netlify/gotrue/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"

"github.com/netlify/gotrue/conf"
"github.com/netlify/gotrue/models"
)

type AdminTestSuite struct {
Expand Down
9 changes: 6 additions & 3 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import (
"github.com/didip/tollbooth/v5"
"github.com/didip/tollbooth/v5/limiter"
"github.com/go-chi/chi"
"github.com/sirupsen/logrus"

"github.com/netlify/gotrue/conf"
"github.com/netlify/gotrue/mailer"
"github.com/netlify/gotrue/observability"
"github.com/netlify/gotrue/storage"

"github.com/rs/cors"
"github.com/sebest/xff"
"github.com/sirupsen/logrus"

"github.com/netlify/gotrue/mailer"
"github.com/netlify/gotrue/observability"
)

const (
Expand Down
9 changes: 5 additions & 4 deletions api/audit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import (
"time"

jwt "github.com/golang-jwt/jwt"
"github.com/netlify/gotrue/conf"
"github.com/netlify/gotrue/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"

"github.com/netlify/gotrue/conf"
"github.com/netlify/gotrue/models"
)

type AuditTestSuite struct {
Expand Down Expand Up @@ -49,12 +50,12 @@ func (ts *AuditTestSuite) makeSuperAdmin(email string) string {
u.Role = "supabase_admin"

var token string
token, err = generateAccessToken(ts.API.db, u, nil, time.Second*time.Duration(ts.Config.JWT.Exp), ts.Config.JWT.Secret)
token, err = generateAccessToken(ts.API.db, u, nil, time.Second*time.Duration(ts.Config.JWT.Exp), ts.Config.JWT.GetSigningMethod(), ts.Config.JWT.GetSigningKey())
require.NoError(ts.T(), err, "Error generating access token")

p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}
_, err = p.Parse(token, func(token *jwt.Token) (interface{}, error) {
return []byte(ts.Config.JWT.Secret), nil
return ts.Config.JWT.GetVerificationKey(), nil
})
require.NoError(ts.T(), err, "Error parsing token")

Expand Down
5 changes: 3 additions & 2 deletions api/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/gofrs/uuid"
jwt "github.com/golang-jwt/jwt"

"github.com/netlify/gotrue/models"
"github.com/netlify/gotrue/storage"
)
Expand Down Expand Up @@ -68,9 +69,9 @@ func (a *API) parseJWTClaims(bearer string, r *http.Request) (context.Context, e
ctx := r.Context()
config := a.config

p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}
p := jwt.Parser{ValidMethods: []string{config.JWT.Algorithm}}
token, err := p.ParseWithClaims(bearer, &GoTrueClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(config.JWT.Secret), nil
return config.JWT.GetVerificationKey(), nil
})
if err != nil {
return nil, unauthorizedError("invalid JWT: unable to parse or verify signature, %v", err)
Expand Down
8 changes: 4 additions & 4 deletions api/external.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (a *API) ExternalProviderRedirect(w http.ResponseWriter, r *http.Request) e
log := observability.GetLogEntry(r)
log.WithField("provider", providerType).Info("Redirecting to external provider")

token := jwt.NewWithClaims(jwt.SigningMethodHS256, ExternalProviderClaims{
token := jwt.NewWithClaims(config.JWT.GetSigningMethod(), ExternalProviderClaims{
NetlifyMicroserviceClaims: NetlifyMicroserviceClaims{
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(5 * time.Minute).Unix(),
Expand All @@ -77,7 +77,7 @@ func (a *API) ExternalProviderRedirect(w http.ResponseWriter, r *http.Request) e
InviteToken: inviteToken,
Referrer: redirectURL,
})
tokenString, err := token.SignedString([]byte(config.JWT.Secret))
tokenString, err := token.SignedString(config.JWT.GetSigningKey())
if err != nil {
return internalServerError("Error creating state").WithInternalError(err)
}
Expand Down Expand Up @@ -424,9 +424,9 @@ func (a *API) processInvite(r *http.Request, ctx context.Context, tx *storage.Co
func (a *API) loadExternalState(ctx context.Context, state string) (context.Context, error) {
config := a.config
claims := ExternalProviderClaims{}
p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}
p := jwt.Parser{ValidMethods: []string{config.JWT.Algorithm}}
_, err := p.ParseWithClaims(state, &claims, func(token *jwt.Token) (interface{}, error) {
return []byte(config.JWT.Secret), nil
return config.JWT.GetVerificationKey(), nil
})
if err != nil || claims.Provider == "" {
return nil, badRequestError("OAuth state is invalid: %v", err)
Expand Down
2 changes: 1 addition & 1 deletion api/external_apple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func (ts *ExternalTestSuite) TestSignupExternalApple() {
claims := ExternalProviderClaims{}
p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}
_, err = p.ParseWithClaims(q.Get("state"), &claims, func(token *jwt.Token) (interface{}, error) {
return []byte(ts.Config.JWT.Secret), nil
return ts.Config.JWT.GetVerificationKey(), nil
})
ts.Require().NoError(err)

Expand Down
2 changes: 1 addition & 1 deletion api/external_azure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func (ts *ExternalTestSuite) TestSignupExternalAzure() {
claims := ExternalProviderClaims{}
p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}
_, err = p.ParseWithClaims(q.Get("state"), &claims, func(token *jwt.Token) (interface{}, error) {
return []byte(ts.Config.JWT.Secret), nil
return ts.Config.JWT.GetVerificationKey(), nil
})
ts.Require().NoError(err)

Expand Down
2 changes: 1 addition & 1 deletion api/external_bitbucket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func (ts *ExternalTestSuite) TestSignupExternalBitbucket() {
claims := ExternalProviderClaims{}
p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}
_, err = p.ParseWithClaims(q.Get("state"), &claims, func(token *jwt.Token) (interface{}, error) {
return []byte(ts.Config.JWT.Secret), nil
return ts.Config.JWT.GetVerificationKey(), nil
})
ts.Require().NoError(err)

Expand Down
2 changes: 1 addition & 1 deletion api/external_discord_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func (ts *ExternalTestSuite) TestSignupExternalDiscord() {
claims := ExternalProviderClaims{}
p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}
_, err = p.ParseWithClaims(q.Get("state"), &claims, func(token *jwt.Token) (interface{}, error) {
return []byte(ts.Config.JWT.Secret), nil
return ts.Config.JWT.GetVerificationKey(), nil
})
ts.Require().NoError(err)

Expand Down
2 changes: 1 addition & 1 deletion api/external_facebook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func (ts *ExternalTestSuite) TestSignupExternalFacebook() {
claims := ExternalProviderClaims{}
p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}
_, err = p.ParseWithClaims(q.Get("state"), &claims, func(token *jwt.Token) (interface{}, error) {
return []byte(ts.Config.JWT.Secret), nil
return ts.Config.JWT.GetVerificationKey(), nil
})
ts.Require().NoError(err)

Expand Down
2 changes: 1 addition & 1 deletion api/external_github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func (ts *ExternalTestSuite) TestSignupExternalGithub() {
claims := ExternalProviderClaims{}
p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}
_, err = p.ParseWithClaims(q.Get("state"), &claims, func(token *jwt.Token) (interface{}, error) {
return []byte(ts.Config.JWT.Secret), nil
return ts.Config.JWT.GetVerificationKey(), nil
})
ts.Require().NoError(err)

Expand Down
2 changes: 1 addition & 1 deletion api/external_gitlab_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func (ts *ExternalTestSuite) TestSignupExternalGitlab() {
claims := ExternalProviderClaims{}
p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}
_, err = p.ParseWithClaims(q.Get("state"), &claims, func(token *jwt.Token) (interface{}, error) {
return []byte(ts.Config.JWT.Secret), nil
return ts.Config.JWT.GetVerificationKey(), nil
})
ts.Require().NoError(err)

Expand Down
2 changes: 1 addition & 1 deletion api/external_google_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func (ts *ExternalTestSuite) TestSignupExternalGoogle() {
claims := ExternalProviderClaims{}
p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}
_, err = p.ParseWithClaims(q.Get("state"), &claims, func(token *jwt.Token) (interface{}, error) {
return []byte(ts.Config.JWT.Secret), nil
return ts.Config.JWT.GetVerificationKey(), nil
})
ts.Require().NoError(err)

Expand Down
2 changes: 1 addition & 1 deletion api/external_twitch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func (ts *ExternalTestSuite) TestSignupExternalTwitch() {
claims := ExternalProviderClaims{}
p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}
_, err = p.ParseWithClaims(q.Get("state"), &claims, func(token *jwt.Token) (interface{}, error) {
return []byte(ts.Config.JWT.Secret), nil
return ts.Config.JWT.GetVerificationKey(), nil
})
ts.Require().NoError(err)

Expand Down
10 changes: 5 additions & 5 deletions api/invite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ import (
"time"

jwt "github.com/golang-jwt/jwt"
"github.com/netlify/gotrue/conf"
"github.com/netlify/gotrue/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"

"github.com/netlify/gotrue/conf"
"github.com/netlify/gotrue/models"
)

type InviteTestSuite struct {
Expand Down Expand Up @@ -59,13 +60,12 @@ func (ts *InviteTestSuite) makeSuperAdmin(email string) string {
u.Role = "supabase_admin"

var token string
token, err = generateAccessToken(ts.API.db, u, nil, time.Second*time.Duration(ts.Config.JWT.Exp), ts.Config.JWT.Secret)

token, err = generateAccessToken(ts.API.db, u, nil, time.Second*time.Duration(ts.Config.JWT.Exp), ts.Config.JWT.GetSigningMethod(), ts.Config.JWT.GetSigningKey())
require.NoError(ts.T(), err, "Error generating access token")

p := jwt.Parser{ValidMethods: []string{jwt.SigningMethodHS256.Name}}
_, err = p.Parse(token, func(token *jwt.Token) (interface{}, error) {
return []byte(ts.Config.JWT.Secret), nil
return ts.Config.JWT.GetVerificationKey(), nil
})
require.NoError(ts.T(), err, "Error parsing token")

Expand Down
7 changes: 4 additions & 3 deletions api/logout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import (
"testing"
"time"

"github.com/netlify/gotrue/conf"
"github.com/netlify/gotrue/models"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"

"github.com/netlify/gotrue/conf"
"github.com/netlify/gotrue/models"
)

type LogoutTestSuite struct {
Expand Down Expand Up @@ -42,7 +43,7 @@ func (ts *LogoutTestSuite) SetupTest() {

// generate access token to use for logout
var t string
t, err = generateAccessToken(ts.API.db, u, nil, time.Second*time.Duration(ts.Config.JWT.Exp), ts.Config.JWT.Secret)
t, err = generateAccessToken(ts.API.db, u, nil, time.Second*time.Duration(ts.Config.JWT.Exp), ts.Config.JWT.GetSigningMethod(), ts.Config.JWT.GetSigningKey())
require.NoError(ts.T(), err)
ts.token = t
}
Expand Down
13 changes: 7 additions & 6 deletions api/mfa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import (
"testing"
"time"

"github.com/pquerna/otp"

"github.com/netlify/gotrue/conf"
"github.com/netlify/gotrue/models"
"github.com/netlify/gotrue/utilities"
"github.com/pquerna/otp"

"github.com/jackc/pgx/v4"

Expand Down Expand Up @@ -124,7 +125,7 @@ func (ts *MFATestSuite) TestEnrollFactor() {
user, err := models.FindUserByEmailAndAudience(ts.API.db, "[email protected]", ts.Config.JWT.Aud)
ts.Require().NoError(err)

token, err := generateAccessToken(ts.API.db, user, nil, time.Second*time.Duration(ts.Config.JWT.Exp), ts.Config.JWT.Secret)
token, err := generateAccessToken(ts.API.db, user, nil, time.Second*time.Duration(ts.Config.JWT.Exp), ts.Config.JWT.GetSigningMethod(), ts.Config.JWT.GetSigningKey())
require.NoError(ts.T(), err)

w := httptest.NewRecorder()
Expand Down Expand Up @@ -161,7 +162,7 @@ func (ts *MFATestSuite) TestChallengeFactor() {
require.NoError(ts.T(), err)
f := factors[0]

token, err := generateAccessToken(ts.API.db, u, nil, time.Second*time.Duration(ts.Config.JWT.Exp), ts.Config.JWT.Secret)
token, err := generateAccessToken(ts.API.db, u, nil, time.Second*time.Duration(ts.Config.JWT.Exp), ts.Config.JWT.GetSigningMethod(), ts.Config.JWT.GetSigningKey())
require.NoError(ts.T(), err, "Error generating access token")

var buffer bytes.Buffer
Expand Down Expand Up @@ -222,7 +223,7 @@ func (ts *MFATestSuite) TestMFAVerifyFactor() {
secondarySession.FactorID = &f.ID
require.NoError(ts.T(), ts.API.db.Create(secondarySession), "Error saving test session")

token, err := generateAccessToken(ts.API.db, user, r.SessionId, time.Second*time.Duration(ts.Config.JWT.Exp), ts.Config.JWT.Secret)
token, err := generateAccessToken(ts.API.db, user, r.SessionId, time.Second*time.Duration(ts.Config.JWT.Exp), ts.Config.JWT.GetSigningMethod(), ts.Config.JWT.GetSigningKey())

require.NoError(ts.T(), err)

Expand Down Expand Up @@ -318,7 +319,7 @@ func (ts *MFATestSuite) TestUnenrollVerifiedFactor() {

var buffer bytes.Buffer

token, err := generateAccessToken(ts.API.db, u, &s.ID, time.Second*time.Duration(ts.Config.JWT.Exp), ts.Config.JWT.Secret)
token, err := generateAccessToken(ts.API.db, u, &s.ID, time.Second*time.Duration(ts.Config.JWT.Exp), ts.Config.JWT.GetSigningMethod(), ts.Config.JWT.GetSigningKey())
require.NoError(ts.T(), err)

w := httptest.NewRecorder()
Expand Down Expand Up @@ -360,7 +361,7 @@ func (ts *MFATestSuite) TestUnenrollUnverifiedFactor() {

var buffer bytes.Buffer

token, err := generateAccessToken(ts.API.db, u, &s.ID, time.Second*time.Duration(ts.Config.JWT.Exp), ts.Config.JWT.Secret)
token, err := generateAccessToken(ts.API.db, u, &s.ID, time.Second*time.Duration(ts.Config.JWT.Exp), ts.Config.JWT.GetSigningMethod(), ts.Config.JWT.GetSigningKey())
require.NoError(ts.T(), err)
require.NoError(ts.T(), json.NewEncoder(&buffer).Encode(map[string]interface{}{
"factor_id": f.ID,
Expand Down
Loading

0 comments on commit 1315789

Please sign in to comment.