feat: add admin session api
This commit is contained in:
parent
e35d0cbcd6
commit
217410e9a4
|
@ -3,6 +3,7 @@ package test
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/resolvers"
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -18,7 +19,7 @@ func aminLoginTests(s TestSetup, t *testing.T) {
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
res, err := resolvers.AdminLoginResolver(ctx, model.AdminLoginInput{
|
res, err := resolvers.AdminLoginResolver(ctx, model.AdminLoginInput{
|
||||||
AdminSecret: "admin",
|
AdminSecret: constants.EnvData.ADMIN_SECRET,
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
28
server/__test__/admin_session_test.go
Normal file
28
server/__test__/admin_session_test.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func aminSessionTests(s TestSetup, t *testing.T) {
|
||||||
|
t.Run(`should get admin session`, func(t *testing.T) {
|
||||||
|
req, ctx := createContext(s)
|
||||||
|
_, err := resolvers.AdminSession(ctx)
|
||||||
|
log.Println("error:", err)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
|
h, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
req.Header.Add("Authorization", "Bearer "+h)
|
||||||
|
res, err := resolvers.AdminSession(ctx)
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Greater(t, len(res.AccessToken), 0)
|
||||||
|
})
|
||||||
|
}
|
|
@ -43,6 +43,7 @@ func TestResolvers(t *testing.T) {
|
||||||
deleteUserTest(s, t)
|
deleteUserTest(s, t)
|
||||||
updateUserTest(s, t)
|
updateUserTest(s, t)
|
||||||
aminLoginTests(s, t)
|
aminLoginTests(s, t)
|
||||||
|
aminSessionTests(s, t)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,6 @@ type DirectiveRoot struct {
|
||||||
type ComplexityRoot struct {
|
type ComplexityRoot struct {
|
||||||
AdminLoginResponse struct {
|
AdminLoginResponse struct {
|
||||||
AccessToken func(childComplexity int) int
|
AccessToken func(childComplexity int) int
|
||||||
ExpiresAt func(childComplexity int) int
|
|
||||||
Message func(childComplexity int) int
|
Message func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -175,13 +174,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||||
|
|
||||||
return e.complexity.AdminLoginResponse.AccessToken(childComplexity), true
|
return e.complexity.AdminLoginResponse.AccessToken(childComplexity), true
|
||||||
|
|
||||||
case "AdminLoginResponse.expires_at":
|
|
||||||
if e.complexity.AdminLoginResponse.ExpiresAt == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return e.complexity.AdminLoginResponse.ExpiresAt(childComplexity), true
|
|
||||||
|
|
||||||
case "AdminLoginResponse.message":
|
case "AdminLoginResponse.message":
|
||||||
if e.complexity.AdminLoginResponse.Message == nil {
|
if e.complexity.AdminLoginResponse.Message == nil {
|
||||||
break
|
break
|
||||||
|
@ -772,7 +764,6 @@ type Response {
|
||||||
type AdminLoginResponse {
|
type AdminLoginResponse {
|
||||||
message: String!
|
message: String!
|
||||||
access_token: String!
|
access_token: String!
|
||||||
expires_at: Int64!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input AdminLoginInput {
|
input AdminLoginInput {
|
||||||
|
@ -1193,41 +1184,6 @@ func (ec *executionContext) _AdminLoginResponse_access_token(ctx context.Context
|
||||||
return ec.marshalNString2string(ctx, field.Selections, res)
|
return ec.marshalNString2string(ctx, field.Selections, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) _AdminLoginResponse_expires_at(ctx context.Context, field graphql.CollectedField, obj *model.AdminLoginResponse) (ret graphql.Marshaler) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
ec.Error(ctx, ec.Recover(ctx, r))
|
|
||||||
ret = graphql.Null
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
fc := &graphql.FieldContext{
|
|
||||||
Object: "AdminLoginResponse",
|
|
||||||
Field: field,
|
|
||||||
Args: nil,
|
|
||||||
IsMethod: false,
|
|
||||||
IsResolver: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx = graphql.WithFieldContext(ctx, fc)
|
|
||||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
|
|
||||||
ctx = rctx // use context from middleware stack in children
|
|
||||||
return obj.ExpiresAt, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
ec.Error(ctx, err)
|
|
||||||
return graphql.Null
|
|
||||||
}
|
|
||||||
if resTmp == nil {
|
|
||||||
if !graphql.HasFieldError(ctx, fc) {
|
|
||||||
ec.Errorf(ctx, "must not be null")
|
|
||||||
}
|
|
||||||
return graphql.Null
|
|
||||||
}
|
|
||||||
res := resTmp.(int64)
|
|
||||||
fc.Result = res
|
|
||||||
return ec.marshalNInt642int64(ctx, field.Selections, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ec *executionContext) _AuthResponse_message(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) {
|
func (ec *executionContext) _AuthResponse_message(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if r := recover(); r != nil {
|
if r := recover(); r != nil {
|
||||||
|
@ -4979,11 +4935,6 @@ func (ec *executionContext) _AdminLoginResponse(ctx context.Context, sel ast.Sel
|
||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
invalids++
|
invalids++
|
||||||
}
|
}
|
||||||
case "expires_at":
|
|
||||||
out.Values[i] = ec._AdminLoginResponse_expires_at(ctx, field, obj)
|
|
||||||
if out.Values[i] == graphql.Null {
|
|
||||||
invalids++
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
panic("unknown field " + strconv.Quote(field.Name))
|
panic("unknown field " + strconv.Quote(field.Name))
|
||||||
}
|
}
|
||||||
|
@ -5754,21 +5705,6 @@ func (ec *executionContext) marshalNID2string(ctx context.Context, sel ast.Selec
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) unmarshalNInt642int64(ctx context.Context, v interface{}) (int64, error) {
|
|
||||||
res, err := graphql.UnmarshalInt64(v)
|
|
||||||
return res, graphql.ErrorOnPath(ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ec *executionContext) marshalNInt642int64(ctx context.Context, sel ast.SelectionSet, v int64) graphql.Marshaler {
|
|
||||||
res := graphql.MarshalInt64(v)
|
|
||||||
if res == graphql.Null {
|
|
||||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
|
||||||
ec.Errorf(ctx, "must not be null")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ec *executionContext) unmarshalNLoginInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐLoginInput(ctx context.Context, v interface{}) (model.LoginInput, error) {
|
func (ec *executionContext) unmarshalNLoginInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐLoginInput(ctx context.Context, v interface{}) (model.LoginInput, error) {
|
||||||
res, err := ec.unmarshalInputLoginInput(ctx, v)
|
res, err := ec.unmarshalInputLoginInput(ctx, v)
|
||||||
return res, graphql.ErrorOnPath(ctx, err)
|
return res, graphql.ErrorOnPath(ctx, err)
|
||||||
|
|
|
@ -9,7 +9,6 @@ type AdminLoginInput struct {
|
||||||
type AdminLoginResponse struct {
|
type AdminLoginResponse struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
AccessToken string `json:"access_token"`
|
AccessToken string `json:"access_token"`
|
||||||
ExpiresAt int64 `json:"expires_at"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthResponse struct {
|
type AuthResponse struct {
|
||||||
|
|
|
@ -65,7 +65,6 @@ type Response {
|
||||||
type AdminLoginResponse {
|
type AdminLoginResponse {
|
||||||
message: String!
|
message: String!
|
||||||
access_token: String!
|
access_token: String!
|
||||||
expires_at: Int64!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input AdminLoginInput {
|
input AdminLoginInput {
|
||||||
|
|
|
@ -5,7 +5,6 @@ package graph
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/graph/generated"
|
"github.com/authorizerdev/authorizer/server/graph/generated"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
@ -81,7 +80,7 @@ func (r *queryResolver) VerificationRequests(ctx context.Context) ([]*model.Veri
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *queryResolver) AdminSession(ctx context.Context) (*model.AdminLoginResponse, error) {
|
func (r *queryResolver) AdminSession(ctx context.Context) (*model.AdminLoginResponse, error) {
|
||||||
panic(fmt.Errorf("not implemented"))
|
return resolvers.AdminSession(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mutation returns generated.MutationResolver implementation.
|
// Mutation returns generated.MutationResolver implementation.
|
||||||
|
|
|
@ -3,38 +3,32 @@ package resolvers
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/enum"
|
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/session"
|
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AdminLoginResolver(ctx context.Context, params model.AdminLoginInput) (*model.AdminLoginResponse, error) {
|
func AdminLoginResolver(ctx context.Context, params model.AdminLoginInput) (*model.AdminLoginResponse, error) {
|
||||||
gc, err := utils.GinContextFromContext(ctx)
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
var res *model.AdminLoginResponse
|
var res *model.AdminLoginResponse
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("=> error:", err)
|
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.AdminSecret != constants.EnvData.ADMIN_SECRET {
|
if params.AdminSecret != constants.EnvData.ADMIN_SECRET {
|
||||||
return nil, fmt.Errorf(`invalid admin secret`)
|
return res, fmt.Errorf(`invalid admin secret`)
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshToken, _, _ := utils.CreateAdminAuthToken(enum.RefreshToken, gc)
|
hashedKey, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
|
||||||
accessToken, expiresAt, _ := utils.CreateAdminAuthToken(enum.AccessToken, gc)
|
if err != nil {
|
||||||
|
return res, err
|
||||||
currentTime := time.Now().Unix()
|
}
|
||||||
tokenId := fmt.Sprintf("authorizer_admin_%d", currentTime)
|
utils.SetAdminCookie(gc, hashedKey)
|
||||||
session.SetToken(tokenId, accessToken, refreshToken)
|
|
||||||
utils.SetAdminCookie(gc, accessToken)
|
|
||||||
|
|
||||||
res = &model.AdminLoginResponse{
|
res = &model.AdminLoginResponse{
|
||||||
AccessToken: accessToken,
|
AccessToken: hashedKey,
|
||||||
ExpiresAt: expiresAt,
|
|
||||||
Message: "admin logged in successfully",
|
Message: "admin logged in successfully",
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
|
|
35
server/resolvers/admin_session.go
Normal file
35
server/resolvers/admin_session.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AdminSession(ctx context.Context) (*model.AdminLoginResponse, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
var res *model.AdminLoginResponse
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !utils.IsSuperAdmin(gc) {
|
||||||
|
return res, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
hashedKey, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
utils.SetAdminCookie(gc, hashedKey)
|
||||||
|
|
||||||
|
res = &model.AdminLoginResponse{
|
||||||
|
AccessToken: hashedKey,
|
||||||
|
Message: "admin logged in successfully",
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (string, int64, error) {
|
func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (string, int64, error) {
|
||||||
|
@ -124,32 +125,8 @@ func VerifyAuthToken(token string) (map[string]interface{}, error) {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateAdminAuthToken(tokenType enum.TokenType, c *gin.Context) (string, int64, error) {
|
func CreateAdminAuthToken(tokenType enum.TokenType, c *gin.Context) (string, error) {
|
||||||
t := jwt.New(jwt.GetSigningMethod(constants.EnvData.JWT_TYPE))
|
return HashPassword(constants.EnvData.ADMIN_SECRET)
|
||||||
expiryBound := time.Hour
|
|
||||||
if tokenType == enum.RefreshToken {
|
|
||||||
// expires in 1 year
|
|
||||||
expiryBound = time.Hour * 8760
|
|
||||||
}
|
|
||||||
|
|
||||||
expiresAt := time.Now().Add(expiryBound).Unix()
|
|
||||||
|
|
||||||
customClaims := jwt.MapClaims{
|
|
||||||
"exp": expiresAt,
|
|
||||||
"iat": time.Now().Unix(),
|
|
||||||
"user_agent": GetUserAgent(c.Request),
|
|
||||||
"ip": GetIP(c.Request),
|
|
||||||
"role": "authorizer_admin",
|
|
||||||
"created_at": time.Now().Unix(),
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Claims = customClaims
|
|
||||||
|
|
||||||
token, err := t.SignedString([]byte(constants.EnvData.JWT_SECRET))
|
|
||||||
if err != nil {
|
|
||||||
return "", 0, err
|
|
||||||
}
|
|
||||||
return token, expiresAt, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAdminAuthToken(gc *gin.Context) (string, error) {
|
func GetAdminAuthToken(gc *gin.Context) (string, error) {
|
||||||
|
@ -162,6 +139,11 @@ func GetAdminAuthToken(gc *gin.Context) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
token = strings.TrimPrefix(auth, "Bearer ")
|
token = strings.TrimPrefix(auth, "Bearer ")
|
||||||
|
|
||||||
|
err = bcrypt.CompareHashAndPassword([]byte(token), []byte(constants.EnvData.ADMIN_SECRET))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf(`unauthorized`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return token, nil
|
return token, nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user