Compare commits

..

26 Commits

Author SHA1 Message Date
Lakhan Samani
b376ee3b73 feat: use multi roles login (#60)
* feat: use multi roles login

- add support for protected roles
- refactor oauth code

* fix: adminUpdate role validation

* fix: update app
2021-10-13 22:11:41 +05:30
Lakhan Samani
27944cf7b5 fix: var access in release action 2021-10-10 08:53:12 +05:30
Lakhan Samani
cab0b54567 fix: var access in release action 2021-10-10 08:50:51 +05:30
Lakhan Samani
a50f8eba57 fix: get the version from meta action 2021-10-10 08:44:52 +05:30
Lakhan Samani
814dc61414 fix: set version based on tags 2021-10-10 08:15:46 +05:30
Lakhan Samani
7abad9db01 fix: version args syntax 2021-10-10 08:10:31 +05:30
Lakhan Samani
17777b111d fix: version args 2021-10-10 08:08:41 +05:30
Lakhan Samani
c7494a0bca fix: syntax 2021-10-10 01:50:36 +05:30
Lakhan Samani
e23d6f92e5 fix: syntax 2021-10-10 01:48:05 +05:30
Lakhan Samani
585091fefb fix: syntax 2021-10-10 01:47:25 +05:30
Lakhan Samani
eabd88718d chore: update github action version 2021-10-10 01:46:17 +05:30
Lakhan Samani
072cd46809 fix: docker build version arg 2021-10-10 01:15:47 +05:30
Lakhan Samani
17676fa13b fix: docker build version arg 2021-10-10 00:28:44 +05:30
Lakhan Samani
8b510ed556 fix: env parsing 2021-10-09 23:49:20 +05:30
Lakhan Samani
3ac0b44446 fix: remove unused env and fix typo 2021-10-09 22:29:10 +05:30
Lakhan Samani
b1dd6f2c3b chore: update app dependencies 2021-10-09 18:27:38 +05:30
Lakhan Samani
f5ea94f63c Merge branch 'main' of https://github.com/authorizerdev/authorizer 2021-10-09 10:06:15 +05:30
Lakhan Samani
653befc737 chore: update app dependencies (#56)
* fix: role validation for given token

* chore: update app dependencies
2021-10-09 10:04:59 +05:30
Lakhan Samani
6fed439ec2 Merge branch 'main' of https://github.com/authorizerdev/authorizer 2021-10-09 10:01:47 +05:30
Lakhan Samani
5bd6fa5bc9 fix: role validation for given token (#55) 2021-10-09 09:40:45 +05:30
Lakhan Samani
75709e9f48 fix: role validation for given token 2021-10-09 09:39:50 +05:30
Lakhan Samani
b1b7f47f4c fix: roles model 2021-10-04 13:58:03 +05:30
Lakhan Samani
e429a1f860 fix: github workflow build script 2021-10-04 13:12:51 +05:30
Lakhan Samani
6819597a79 fix: mac build script 2021-10-04 13:11:26 +05:30
Lakhan Samani
b34b385be5 fix: email template 2021-10-04 12:19:27 +05:30
Lakhan Samani
5acf59d16e feat: add responsive email template + support for org env
Resolves #52
2021-10-04 03:17:50 +05:30
33 changed files with 554 additions and 347 deletions

View File

@@ -5,6 +5,7 @@ ADMIN_SECRET=admin
DISABLE_EMAIL_VERIFICATION=true
JWT_SECRET=random_string
JWT_TYPE=HS256
ROLES=user,admin
DEFAULT_ROLE=user
ROLES=user
DEFAULT_ROLES=user
PROTECTED_ROLES=admin
JWT_ROLE_CLAIM=role

View File

@@ -41,32 +41,34 @@ jobs:
make clean && \
CGO_ENABLED=1 GOOS=windows CC=/usr/bin/x86_64-w64-mingw32-gcc make && \
mv build/server build/server.exe && \
zip -vr authorizer-${VERSION}-windows-amd64.zip .env app build templates
zip -vr authorizer-${VERSION}-windows-amd64.zip .env app/build build templates
- name: Package files for linux
run: |
make clean && \
CGO_ENABLED=1 make && \
tar cvfz authorizer-${VERSION}-linux-amd64.tar.gz .env app build templates
tar cvfz authorizer-${VERSION}-linux-amd64.tar.gz .env app/build build templates
- name: Upload assets
run: |
github-assets-uploader -f authorizer-${VERSION}-windows-amd64.zip -mediatype application/zip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION} && \
github-assets-uploader -f authorizer-${VERSION}-linux-amd64.tar.gz -mediatype application/gzip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION}
- name: Log in to Docker Hub
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
uses: docker/metadata-action@v3
with:
images: lakhansamani/authorizer
- name: Build and push Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
uses: docker/build-push-action@v2
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }}

View File

@@ -3,9 +3,10 @@ WORKDIR /app
COPY server server
COPY Makefile .
ARG VERSION=0.1.0-beta.0
ENV VERSION="${VERSION}"
ARG VERSION="latest"
ENV VERSION="$VERSION"
RUN echo "$VERSION"
RUN apk add build-base &&\
make clean && make && \
chmod 777 build/server

View File

@@ -14,3 +14,9 @@ For the first version we will only support setting roles master list via env
- [x] Return roles in users list for super admin
- [x] Add roles to the JWT token generation
- [x] Validate token should also validate the role, if roles to validate again is present in request
# Misc
- [x] Fix email template
- [x] Add support for organization name in .env
- [x] Add support for organization logo in .env

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

30
app/package-lock.json generated
View File

@@ -8,7 +8,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@authorizerdev/authorizer-react": "^0.1.0-beta.16",
"@authorizerdev/authorizer-react": "^0.1.0-beta.19",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17",
@@ -22,9 +22,9 @@
}
},
"node_modules/@authorizerdev/authorizer-js": {
"version": "0.1.0-beta.16",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.1.0-beta.16.tgz",
"integrity": "sha512-RyjWhVbLYvmkcAT+2ddpRhrt7P5DBVKtGKjHWRugSqenTW7XFQMlhQqx5Z09DeqLw3Lsq0VVZ5h8tWcExHvwEw==",
"version": "0.1.0-beta.22",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.1.0-beta.22.tgz",
"integrity": "sha512-fHyZDL49eEsbxxIb2xxW6iLM+/N60m5e2NeVVlMFZjZFX9eUetdFatCOrPTuukGul7l8wO6YofTnwqOLOVjKrA==",
"dependencies": {
"node-fetch": "^2.6.1"
},
@@ -33,11 +33,11 @@
}
},
"node_modules/@authorizerdev/authorizer-react": {
"version": "0.1.0-beta.16",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.1.0-beta.16.tgz",
"integrity": "sha512-b00LH0gtfMh/opFaGDF+EkxDOpNkCj/TNVjyW2wbiOFdC4HgLhc+UbPgL5/rDDol4XQsToL3SmwxmSxB2lWWuQ==",
"version": "0.1.0-beta.19",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.1.0-beta.19.tgz",
"integrity": "sha512-ScAG3Auu0KirxuxpQ25JGtYCDmiC/3DM/ngnGO+XBaW+bWUARifH2HPkOnddUxkKPAfrKyxoa/l730W8mxhy+A==",
"dependencies": {
"@authorizerdev/authorizer-js": "^0.1.0-beta.16",
"@authorizerdev/authorizer-js": "^0.1.0-beta.22",
"final-form": "^4.20.2",
"react-final-form": "^6.5.3",
"styled-components": "^5.3.0"
@@ -797,19 +797,19 @@
},
"dependencies": {
"@authorizerdev/authorizer-js": {
"version": "0.1.0-beta.16",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.1.0-beta.16.tgz",
"integrity": "sha512-RyjWhVbLYvmkcAT+2ddpRhrt7P5DBVKtGKjHWRugSqenTW7XFQMlhQqx5Z09DeqLw3Lsq0VVZ5h8tWcExHvwEw==",
"version": "0.1.0-beta.22",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.1.0-beta.22.tgz",
"integrity": "sha512-fHyZDL49eEsbxxIb2xxW6iLM+/N60m5e2NeVVlMFZjZFX9eUetdFatCOrPTuukGul7l8wO6YofTnwqOLOVjKrA==",
"requires": {
"node-fetch": "^2.6.1"
}
},
"@authorizerdev/authorizer-react": {
"version": "0.1.0-beta.16",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.1.0-beta.16.tgz",
"integrity": "sha512-b00LH0gtfMh/opFaGDF+EkxDOpNkCj/TNVjyW2wbiOFdC4HgLhc+UbPgL5/rDDol4XQsToL3SmwxmSxB2lWWuQ==",
"version": "0.1.0-beta.19",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.1.0-beta.19.tgz",
"integrity": "sha512-ScAG3Auu0KirxuxpQ25JGtYCDmiC/3DM/ngnGO+XBaW+bWUARifH2HPkOnddUxkKPAfrKyxoa/l730W8mxhy+A==",
"requires": {
"@authorizerdev/authorizer-js": "^0.1.0-beta.16",
"@authorizerdev/authorizer-js": "^0.1.0-beta.22",
"final-form": "^4.20.2",
"react-final-form": "^6.5.3",
"styled-components": "^5.3.0"

View File

@@ -10,7 +10,7 @@
"author": "Lakhan Samani",
"license": "ISC",
"dependencies": {
"@authorizerdev/authorizer-react": "^0.1.0-beta.16",
"@authorizerdev/authorizer-react": "^0.1.0-beta.19",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17",

View File

@@ -4,30 +4,52 @@ import { AuthorizerProvider } from '@authorizerdev/authorizer-react';
import Root from './Root';
export default function App() {
// @ts-ignore
const globalState: Record<string, string> = window['__authorizer__'];
return (
<div style={{ display: 'flex', justifyContent: 'center' }}>
<div
style={{
width: 400,
margin: `10px auto`,
border: `1px solid #D1D5DB`,
padding: `25px 20px`,
borderRadius: 5,
}}
>
<BrowserRouter>
<AuthorizerProvider
config={{
authorizerURL: globalState.authorizerURL,
redirectURL: globalState.redirectURL,
}}
>
<Root />
</AuthorizerProvider>
</BrowserRouter>
</div>
</div>
);
// @ts-ignore
const globalState: Record<string, string> = window['__authorizer__'];
return (
<div
style={{
display: 'flex',
justifyContent: 'center',
flexDirection: 'column',
}}
>
<div
style={{
display: 'flex',
justifyContent: 'center',
marginTop: 20,
flexDirection: 'column',
alignItems: 'center',
}}
>
<img
src={`${globalState.organizationLogo}`}
alt="logo"
style={{ height: 60, width: 60, objectFit: 'cover' }}
/>
<h1>{globalState.organizationName}</h1>
</div>
<div
style={{
width: 400,
margin: `10px auto`,
border: `1px solid #D1D5DB`,
padding: `25px 20px`,
borderRadius: 5,
}}
>
<BrowserRouter>
<AuthorizerProvider
config={{
authorizerURL: globalState.authorizerURL,
redirectURL: globalState.redirectURL,
}}
>
<Root />
</AuthorizerProvider>
</BrowserRouter>
</div>
</div>
);
}

View File

@@ -10,9 +10,9 @@ export default function Root() {
useEffect(() => {
if (token) {
const url = new URL(config.redirectURL);
const url = new URL(config.redirectURL || '/app');
if (url.origin !== window.location.origin) {
window.location.href = config.redirectURL;
window.location.href = config.redirectURL || '/app';
}
}
return () => {};

View File

@@ -2,9 +2,9 @@ import React, { Fragment } from 'react';
import { Authorizer } from '@authorizerdev/authorizer-react';
export default function Login() {
return (
<Fragment>
<Authorizer />
</Fragment>
);
return (
<Fragment>
<Authorizer />
</Fragment>
);
}

View File

@@ -1,7 +1,7 @@
VERSION="$1"
make clean && CGO_ENABLED=1 VERSION=${VERSION} make
FILE_NAME=authorizer-${VERSION}-darwin-amd64.tar.gz
tar cvfz ${FILE_NAME} .env app build templates
tar cvfz ${FILE_NAME} .env app/build build templates
AUTH="Authorization: token $GITHUB_TOKEN"
RELASE_INFO=$(curl -sH "$AUTH" https://api.github.com/repos/authorizerdev/authorizer/releases/tags/${VERSION})
echo $RELASE_INFO

View File

@@ -13,7 +13,6 @@ var (
JWT_TYPE = ""
JWT_SECRET = ""
ALLOWED_ORIGINS = []string{}
ALLOWED_CALLBACK_URLS = []string{}
AUTHORIZER_URL = ""
PORT = "8080"
REDIS_URL = ""
@@ -24,9 +23,10 @@ var (
DISABLE_BASIC_AUTHENTICATION = "false"
// ROLES
ROLES = []string{}
DEFAULT_ROLE = ""
JWT_ROLE_CLAIM = "role"
ROLES = []string{}
PROTECTED_ROLES = []string{}
DEFAULT_ROLES = []string{}
JWT_ROLE_CLAIM = "role"
// OAuth login
GOOGLE_CLIENT_ID = ""
@@ -37,4 +37,8 @@ var (
FACEBOOK_CLIENT_SECRET = ""
TWITTER_CLIENT_ID = ""
TWITTER_CLIENT_SECRET = ""
// Org envs
ORGANIZATION_NAME = "Authorizer"
ORGANIZATION_LOGO = "https://authorizer.dev/images/logo.png"
)

View File

@@ -23,8 +23,7 @@ func (r *Role) BeforeCreate(tx *gorm.DB) (err error) {
func (mgr *manager) SaveRoles(roles []Role) error {
res := mgr.db.Clauses(
clause.OnConflict{
OnConstraint: "authorizer_roles_role_key",
DoNothing: true,
DoNothing: true,
}).Create(&roles)
if res.Error != nil {
log.Println(`Error saving roles`)

View File

@@ -7,47 +7,38 @@ import (
"strings"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/joho/godotenv"
)
// build variables
var Version string
// ParseArgs -> to parse the cli flag and get db url. This is useful with heroku button
func ParseArgs() {
dbURL := flag.String("database_url", "", "Database connection string")
dbType := flag.String("databse_type", "", "Database type, possible values are postgres,mysql,sqlite")
authorizerURL := flag.String("authorizer_url", "", "URL for authorizer instance, eg: https://xyz.herokuapp.com")
flag.Parse()
if *dbURL != "" {
constants.DATABASE_URL = *dbURL
}
if *dbType != "" {
constants.DATABASE_TYPE = *dbType
}
if *authorizerURL != "" {
constants.AUTHORIZER_URL = *authorizerURL
}
}
var (
Version string
ARG_DB_URL *string
ARG_DB_TYPE *string
ARG_AUTHORIZER_URL *string
ARG_ENV_FILE *string
)
// InitEnv -> to initialize env and through error if required env are not present
func InitEnv() {
envPath := `.env`
envFile := flag.String("env_file", "", "Env file path")
ARG_DB_URL = flag.String("database_url", "", "Database connection string")
ARG_DB_TYPE = flag.String("database_type", "", "Database type, possible values are postgres,mysql,sqlite")
ARG_AUTHORIZER_URL = flag.String("authorizer_url", "", "URL for authorizer instance, eg: https://xyz.herokuapp.com")
ARG_ENV_FILE = flag.String("env_file", "", "Env file path")
flag.Parse()
if *envFile != "" {
envPath = *envFile
if *ARG_ENV_FILE != "" {
envPath = *ARG_ENV_FILE
}
err := godotenv.Load(envPath)
if err != nil {
log.Println("Error loading .env file")
}
constants.VERSION = Version
constants.ADMIN_SECRET = os.Getenv("ADMIN_SECRET")
constants.ENV = os.Getenv("ENV")
constants.DATABASE_TYPE = os.Getenv("DATABASE_TYPE")
@@ -73,7 +64,6 @@ func InitEnv() {
constants.RESET_PASSWORD_URL = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/")
constants.DISABLE_BASIC_AUTHENTICATION = os.Getenv("DISABLE_BASIC_AUTHENTICATION")
constants.DISABLE_EMAIL_VERIFICATION = os.Getenv("DISABLE_EMAIL_VERIFICATION")
constants.DEFAULT_ROLE = os.Getenv("DEFAULT_ROLE")
constants.JWT_ROLE_CLAIM = os.Getenv("JWT_ROLE_CLAIM")
if constants.ADMIN_SECRET == "" {
@@ -104,20 +94,18 @@ func InitEnv() {
}
constants.ALLOWED_ORIGINS = allowedOrigins
allowedCallbackSplit := strings.Split(os.Getenv("ALLOWED_CALLBACK_URLS"), ",")
allowedCallbacks := []string{}
for _, val := range allowedCallbackSplit {
trimVal := strings.TrimSpace(val)
if trimVal != "" {
allowedCallbacks = append(allowedCallbacks, trimVal)
}
if *ARG_AUTHORIZER_URL != "" {
constants.AUTHORIZER_URL = *ARG_AUTHORIZER_URL
}
if *ARG_DB_URL != "" {
constants.DATABASE_URL = *ARG_DB_URL
}
if *ARG_DB_TYPE != "" {
constants.DATABASE_TYPE = *ARG_DB_TYPE
}
if len(allowedCallbackSplit) == 0 {
allowedCallbackSplit = []string{"*"}
}
constants.ALLOWED_CALLBACK_URLS = allowedCallbackSplit
ParseArgs()
if constants.DATABASE_URL == "" {
panic("Database url is required")
}
@@ -148,7 +136,26 @@ func InitEnv() {
rolesSplit := strings.Split(os.Getenv("ROLES"), ",")
roles := []string{}
defaultRole := ""
if len(rolesSplit) == 0 {
roles = []string{"user"}
}
defaultRoleSplit := strings.Split(os.Getenv("DEFAULT_ROLES"), ",")
defaultRoles := []string{}
if len(defaultRoleSplit) == 0 {
defaultRoles = []string{"user"}
}
protectedRolesSplit := strings.Split(os.Getenv("PROTECTED_ROLES"), ",")
protectedRoles := []string{}
if len(protectedRolesSplit) > 0 {
for _, val := range protectedRolesSplit {
trimVal := strings.TrimSpace(val)
protectedRoles = append(protectedRoles, trimVal)
}
}
for _, val := range rolesSplit {
trimVal := strings.TrimSpace(val)
@@ -156,22 +163,28 @@ func InitEnv() {
roles = append(roles, trimVal)
}
if trimVal == constants.DEFAULT_ROLE {
defaultRole = trimVal
if utils.StringContains(defaultRoleSplit, trimVal) {
defaultRoles = append(defaultRoles, trimVal)
}
}
if len(roles) > 0 && defaultRole == "" {
if len(roles) > 0 && len(defaultRoles) == 0 && len(defaultRoleSplit) > 0 {
panic(`Invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`)
}
if len(roles) == 0 {
roles = []string{"user", "admin"}
constants.DEFAULT_ROLE = "user"
}
constants.ROLES = roles
constants.DEFAULT_ROLES = defaultRoles
constants.PROTECTED_ROLES = protectedRoles
if constants.JWT_ROLE_CLAIM == "" {
constants.JWT_ROLE_CLAIM = "role"
}
if os.Getenv("ORGANIZATION_NAME") != "" {
constants.ORGANIZATION_NAME = os.Getenv("ORGANIZATION_NAME")
}
if os.Getenv("ORGANIZATION_LOGO") != "" {
constants.ORGANIZATION_LOGO = os.Getenv("ORGANIZATION_LOGO")
}
}

View File

@@ -81,7 +81,7 @@ type ComplexityRoot struct {
Query struct {
Meta func(childComplexity int) int
Profile func(childComplexity int) int
Token func(childComplexity int, role *string) int
Token func(childComplexity int, roles []string) int
Users func(childComplexity int) int
VerificationRequests func(childComplexity int) int
}
@@ -129,7 +129,7 @@ type MutationResolver interface {
type QueryResolver interface {
Meta(ctx context.Context) (*model.Meta, error)
Users(ctx context.Context) ([]*model.User, error)
Token(ctx context.Context, role *string) (*model.AuthResponse, error)
Token(ctx context.Context, roles []string) (*model.AuthResponse, error)
Profile(ctx context.Context) (*model.User, error)
VerificationRequests(ctx context.Context) ([]*model.VerificationRequest, error)
}
@@ -379,7 +379,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return 0, false
}
return e.complexity.Query.Token(childComplexity, args["role"].(*string)), true
return e.complexity.Query.Token(childComplexity, args["roles"].([]string)), true
case "Query.users":
if e.complexity.Query.Users == nil {
@@ -648,13 +648,13 @@ input SignUpInput {
password: String!
confirmPassword: String!
image: String
roles: [String]
roles: [String!]
}
input LoginInput {
email: String!
password: String!
role: String
roles: [String!]
}
input VerifyEmailInput {
@@ -677,12 +677,12 @@ input UpdateProfileInput {
}
input AdminUpdateUserInput {
id: ID!
email: String
firstName: String
lastName: String
image: String
roles: [String]
id: ID!
email: String
firstName: String
lastName: String
image: String
roles: [String]
}
input ForgotPasswordInput {
@@ -704,7 +704,7 @@ type Mutation {
login(params: LoginInput!): AuthResponse!
logout: Response!
updateProfile(params: UpdateProfileInput!): Response!
adminUpdateUser(params: AdminUpdateUserInput!): User!
adminUpdateUser(params: AdminUpdateUserInput!): User!
verifyEmail(params: VerifyEmailInput!): AuthResponse!
resendVerifyEmail(params: ResendVerifyEmailInput!): Response!
forgotPassword(params: ForgotPasswordInput!): Response!
@@ -715,7 +715,7 @@ type Mutation {
type Query {
meta: Meta!
users: [User!]!
token(role: String): AuthResponse
token(roles: [String!]): AuthResponse
profile: User!
verificationRequests: [VerificationRequest!]!
}
@@ -880,15 +880,15 @@ func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs
func (ec *executionContext) field_Query_token_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 *string
if tmp, ok := rawArgs["role"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("role"))
arg0, err = ec.unmarshalOString2string(ctx, tmp)
var arg0 []string
if tmp, ok := rawArgs["roles"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roles"))
arg0, err = ec.unmarshalOString2string(ctx, tmp)
if err != nil {
return nil, err
}
}
args["role"] = arg0
args["roles"] = arg0
return args, nil
}
@@ -1884,7 +1884,7 @@ func (ec *executionContext) _Query_token(ctx context.Context, field graphql.Coll
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Query().Token(rctx, args["role"].(*string))
return ec.resolvers.Query().Token(rctx, args["roles"].([]string))
})
if err != nil {
ec.Error(ctx, err)
@@ -3842,11 +3842,11 @@ func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj in
if err != nil {
return it, err
}
case "role":
case "roles":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("role"))
it.Role, err = ec.unmarshalOString2string(ctx, v)
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roles"))
it.Roles, err = ec.unmarshalOString2string(ctx, v)
if err != nil {
return it, err
}
@@ -3970,7 +3970,7 @@ func (ec *executionContext) unmarshalInputSignUpInput(ctx context.Context, obj i
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roles"))
it.Roles, err = ec.unmarshalOString2ᚕstring(ctx, v)
it.Roles, err = ec.unmarshalOString2ᚕstring(ctx, v)
if err != nil {
return it, err
}
@@ -5280,6 +5280,42 @@ func (ec *executionContext) marshalOString2string(ctx context.Context, sel ast.S
return graphql.MarshalString(v)
}
func (ec *executionContext) unmarshalOString2ᚕstringᚄ(ctx context.Context, v interface{}) ([]string, error) {
if v == nil {
return nil, nil
}
var vSlice []interface{}
if v != nil {
if tmp1, ok := v.([]interface{}); ok {
vSlice = tmp1
} else {
vSlice = []interface{}{v}
}
}
var err error
res := make([]string, len(vSlice))
for i := range vSlice {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i))
res[i], err = ec.unmarshalNString2string(ctx, vSlice[i])
if err != nil {
return nil, err
}
}
return res, nil
}
func (ec *executionContext) marshalOString2ᚕstringᚄ(ctx context.Context, sel ast.SelectionSet, v []string) graphql.Marshaler {
if v == nil {
return graphql.Null
}
ret := make(graphql.Array, len(v))
for i := range v {
ret[i] = ec.marshalNString2string(ctx, sel, v[i])
}
return ret
}
func (ec *executionContext) unmarshalOString2ᚕᚖstring(ctx context.Context, v interface{}) ([]*string, error) {
if v == nil {
return nil, nil

View File

@@ -32,9 +32,9 @@ type ForgotPasswordInput struct {
}
type LoginInput struct {
Email string `json:"email"`
Password string `json:"password"`
Role *string `json:"role"`
Email string `json:"email"`
Password string `json:"password"`
Roles []string `json:"roles"`
}
type Meta struct {
@@ -62,13 +62,13 @@ type Response struct {
}
type SignUpInput struct {
FirstName *string `json:"firstName"`
LastName *string `json:"lastName"`
Email string `json:"email"`
Password string `json:"password"`
ConfirmPassword string `json:"confirmPassword"`
Image *string `json:"image"`
Roles []*string `json:"roles"`
FirstName *string `json:"firstName"`
LastName *string `json:"lastName"`
Email string `json:"email"`
Password string `json:"password"`
ConfirmPassword string `json:"confirmPassword"`
Image *string `json:"image"`
Roles []string `json:"roles"`
}
type UpdateProfileInput struct {

View File

@@ -61,13 +61,13 @@ input SignUpInput {
password: String!
confirmPassword: String!
image: String
roles: [String]
roles: [String!]
}
input LoginInput {
email: String!
password: String!
role: String
roles: [String!]
}
input VerifyEmailInput {
@@ -90,12 +90,12 @@ input UpdateProfileInput {
}
input AdminUpdateUserInput {
id: ID!
email: String
firstName: String
lastName: String
image: String
roles: [String]
id: ID!
email: String
firstName: String
lastName: String
image: String
roles: [String]
}
input ForgotPasswordInput {
@@ -117,7 +117,7 @@ type Mutation {
login(params: LoginInput!): AuthResponse!
logout: Response!
updateProfile(params: UpdateProfileInput!): Response!
adminUpdateUser(params: AdminUpdateUserInput!): User!
adminUpdateUser(params: AdminUpdateUserInput!): User!
verifyEmail(params: VerifyEmailInput!): AuthResponse!
resendVerifyEmail(params: ResendVerifyEmailInput!): Response!
forgotPassword(params: ForgotPasswordInput!): Response!
@@ -128,7 +128,7 @@ type Mutation {
type Query {
meta: Meta!
users: [User!]!
token(role: String): AuthResponse
token(roles: [String!]): AuthResponse
profile: User!
verificationRequests: [VerificationRequest!]!
}

View File

@@ -59,8 +59,8 @@ func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
return resolvers.Users(ctx)
}
func (r *queryResolver) Token(ctx context.Context, role *string) (*model.AuthResponse, error) {
return resolvers.Token(ctx, role)
func (r *queryResolver) Token(ctx context.Context, roles []string) (*model.AuthResponse, error) {
return resolvers.Token(ctx, roles)
}
func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) {

View File

@@ -75,8 +75,10 @@ func AppHandler() gin.HandlerFunc {
}
c.HTML(http.StatusOK, "app.tmpl", gin.H{
"data": map[string]string{
"authorizerURL": stateObj.AuthorizerURL,
"redirectURL": stateObj.RedirectURL,
"authorizerURL": stateObj.AuthorizerURL,
"redirectURL": stateObj.RedirectURL,
"organizationName": constants.ORGANIZATION_NAME,
"organizationLogo": constants.ORGANIZATION_LOGO,
},
})
}

View File

@@ -19,28 +19,29 @@ import (
"golang.org/x/oauth2"
)
func processGoogleUserInfo(code string, role string, c *gin.Context) error {
func processGoogleUserInfo(code string, roles []string, c *gin.Context) (db.User, error) {
user := db.User{}
token, err := oauth.OAuthProvider.GoogleConfig.Exchange(oauth2.NoContext, code)
if err != nil {
return fmt.Errorf("invalid google exchange code: %s", err.Error())
return user, fmt.Errorf("invalid google exchange code: %s", err.Error())
}
client := oauth.OAuthProvider.GoogleConfig.Client(oauth2.NoContext, token)
response, err := client.Get(constants.GoogleUserInfoURL)
if err != nil {
return err
return user, err
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("failed to read google response body: %s", err.Error())
return user, fmt.Errorf("failed to read google response body: %s", err.Error())
}
userRawData := make(map[string]string)
json.Unmarshal(body, &userRawData)
existingUser, err := db.Mgr.GetUserByEmail(userRawData["email"])
user := db.User{
user = db.User{
FirstName: userRawData["given_name"],
LastName: userRawData["family_name"],
Image: userRawData["picture"],
@@ -50,7 +51,7 @@ func processGoogleUserInfo(code string, role string, c *gin.Context) error {
if err != nil {
// user not registered, register user and generate session token
user.SignupMethod = enum.Google.String()
user.Roles = role
user.Roles = strings.Join(roles, ",")
} else {
// user exists in db, check if method was google
// if not append google to existing signup method and save it
@@ -61,34 +62,25 @@ func processGoogleUserInfo(code string, role string, c *gin.Context) error {
}
user.SignupMethod = signupMethod
user.Password = existingUser.Password
if !utils.IsValidRole(strings.Split(existingUser.Roles, ","), role) {
return fmt.Errorf("invalid role")
if !utils.IsValidRoles(strings.Split(existingUser.Roles, ","), roles) {
return user, fmt.Errorf("invalid role")
}
user.Roles = existingUser.Roles
}
user, _ = db.Mgr.SaveUser(user)
user, _ = db.Mgr.GetUserByEmail(user.Email)
userIdStr := fmt.Sprintf("%v", user.ID)
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, role)
accessToken, _, _ := utils.CreateAuthToken(user, enum.AccessToken, role)
utils.SetCookie(c, accessToken)
session.SetToken(userIdStr, refreshToken)
return nil
return user, nil
}
func processGithubUserInfo(code string, role string, c *gin.Context) error {
func processGithubUserInfo(code string, roles []string, c *gin.Context) (db.User, error) {
user := db.User{}
token, err := oauth.OAuthProvider.GithubConfig.Exchange(oauth2.NoContext, code)
if err != nil {
return fmt.Errorf("invalid github exchange code: %s", err.Error())
return user, fmt.Errorf("invalid github exchange code: %s", err.Error())
}
client := http.Client{}
req, err := http.NewRequest("GET", constants.GithubUserInfoURL, nil)
if err != nil {
return fmt.Errorf("error creating github user info request: %s", err.Error())
return user, fmt.Errorf("error creating github user info request: %s", err.Error())
}
req.Header = http.Header{
"Authorization": []string{fmt.Sprintf("token %s", token.AccessToken)},
@@ -96,13 +88,13 @@ func processGithubUserInfo(code string, role string, c *gin.Context) error {
response, err := client.Do(req)
if err != nil {
return err
return user, err
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("failed to read github response body: %s", err.Error())
return user, fmt.Errorf("failed to read github response body: %s", err.Error())
}
userRawData := make(map[string]string)
@@ -118,7 +110,7 @@ func processGithubUserInfo(code string, role string, c *gin.Context) error {
if len(name) > 1 && strings.TrimSpace(name[1]) != "" {
lastName = name[0]
}
user := db.User{
user = db.User{
FirstName: firstName,
LastName: lastName,
Image: userRawData["avatar_url"],
@@ -128,7 +120,7 @@ func processGithubUserInfo(code string, role string, c *gin.Context) error {
if err != nil {
// user not registered, register user and generate session token
user.SignupMethod = enum.Github.String()
user.Roles = role
user.Roles = strings.Join(roles, ",")
} else {
// user exists in db, check if method was google
// if not append google to existing signup method and save it
@@ -140,45 +132,38 @@ func processGithubUserInfo(code string, role string, c *gin.Context) error {
user.SignupMethod = signupMethod
user.Password = existingUser.Password
if !utils.IsValidRole(strings.Split(existingUser.Roles, ","), role) {
return fmt.Errorf("invalid role")
if !utils.IsValidRoles(strings.Split(existingUser.Roles, ","), roles) {
return user, fmt.Errorf("invalid role")
}
user.Roles = existingUser.Roles
}
user, _ = db.Mgr.SaveUser(user)
user, _ = db.Mgr.GetUserByEmail(user.Email)
userIdStr := fmt.Sprintf("%v", user.ID)
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, role)
accessToken, _, _ := utils.CreateAuthToken(user, enum.AccessToken, role)
utils.SetCookie(c, accessToken)
session.SetToken(userIdStr, refreshToken)
return nil
return user, nil
}
func processFacebookUserInfo(code string, role string, c *gin.Context) error {
func processFacebookUserInfo(code string, roles []string, c *gin.Context) (db.User, error) {
user := db.User{}
token, err := oauth.OAuthProvider.FacebookConfig.Exchange(oauth2.NoContext, code)
if err != nil {
return fmt.Errorf("invalid facebook exchange code: %s", err.Error())
return user, fmt.Errorf("invalid facebook exchange code: %s", err.Error())
}
client := http.Client{}
req, err := http.NewRequest("GET", constants.FacebookUserInfoURL+token.AccessToken, nil)
if err != nil {
return fmt.Errorf("error creating facebook user info request: %s", err.Error())
return user, fmt.Errorf("error creating facebook user info request: %s", err.Error())
}
response, err := client.Do(req)
if err != nil {
log.Println("err:", err)
return err
return user, err
}
defer response.Body.Close()
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return fmt.Errorf("failed to read facebook response body: %s", err.Error())
return user, fmt.Errorf("failed to read facebook response body: %s", err.Error())
}
userRawData := make(map[string]interface{})
@@ -189,7 +174,7 @@ func processFacebookUserInfo(code string, role string, c *gin.Context) error {
picObject := userRawData["picture"].(map[string]interface{})["data"]
picDataObject := picObject.(map[string]interface{})
user := db.User{
user = db.User{
FirstName: fmt.Sprintf("%v", userRawData["first_name"]),
LastName: fmt.Sprintf("%v", userRawData["last_name"]),
Image: fmt.Sprintf("%v", picDataObject["url"]),
@@ -200,7 +185,7 @@ func processFacebookUserInfo(code string, role string, c *gin.Context) error {
if err != nil {
// user not registered, register user and generate session token
user.SignupMethod = enum.Github.String()
user.Roles = role
user.Roles = strings.Join(roles, ",")
} else {
// user exists in db, check if method was google
// if not append google to existing signup method and save it
@@ -212,22 +197,14 @@ func processFacebookUserInfo(code string, role string, c *gin.Context) error {
user.SignupMethod = signupMethod
user.Password = existingUser.Password
if !utils.IsValidRole(strings.Split(existingUser.Roles, ","), role) {
return fmt.Errorf("invalid role")
if !utils.IsValidRoles(strings.Split(existingUser.Roles, ","), roles) {
return user, fmt.Errorf("invalid role")
}
user.Roles = existingUser.Roles
}
user, _ = db.Mgr.SaveUser(user)
user, _ = db.Mgr.GetUserByEmail(user.Email)
userIdStr := fmt.Sprintf("%v", user.ID)
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, role)
accessToken, _, _ := utils.CreateAuthToken(user, enum.AccessToken, role)
utils.SetCookie(c, accessToken)
session.SetToken(userIdStr, refreshToken)
return nil
return user, nil
}
func OAuthCallbackHandler() gin.HandlerFunc {
@@ -249,18 +226,19 @@ func OAuthCallbackHandler() gin.HandlerFunc {
return
}
role := sessionSplit[2]
roles := strings.Split(sessionSplit[2], ",")
redirectURL := sessionSplit[1]
var err error
user := db.User{}
code := c.Request.FormValue("code")
switch provider {
case enum.Google.String():
err = processGoogleUserInfo(code, role, c)
user, err = processGoogleUserInfo(code, roles, c)
case enum.Github.String():
err = processGithubUserInfo(code, role, c)
user, err = processGithubUserInfo(code, roles, c)
case enum.Facebook.String():
err = processFacebookUserInfo(code, role, c)
user, err = processFacebookUserInfo(code, roles, c)
default:
err = fmt.Errorf(`invalid oauth provider`)
}
@@ -269,6 +247,16 @@ func OAuthCallbackHandler() gin.HandlerFunc {
c.JSON(400, gin.H{"error": err.Error()})
return
}
user, _ = db.Mgr.SaveUser(user)
user, _ = db.Mgr.GetUserByEmail(user.Email)
userIdStr := fmt.Sprintf("%v", user.ID)
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, roles)
accessToken, _, _ := utils.CreateAuthToken(user, enum.AccessToken, roles)
utils.SetCookie(c, accessToken)
session.SetToken(userIdStr, refreshToken)
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
}
}

View File

@@ -2,6 +2,7 @@ package handlers
import (
"net/http"
"strings"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/enum"
@@ -18,7 +19,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
return func(c *gin.Context) {
// TODO validate redirect URL
redirectURL := c.Query("redirectURL")
role := c.Query("role")
roles := c.Query("roles")
if redirectURL == "" {
c.JSON(400, gin.H{
@@ -27,20 +28,24 @@ func OAuthLoginHandler() gin.HandlerFunc {
return
}
if role != "" {
if roles != "" {
// validate role
if !utils.IsValidRole(constants.ROLES, role) {
rolesSplit := strings.Split(roles, ",")
// use protected roles verification for admin login only.
// though if not associated with user, it will be rejected from oauth_callback
if !utils.IsValidRoles(append([]string{}, append(constants.ROLES, constants.PROTECTED_ROLES...)...), rolesSplit) {
c.JSON(400, gin.H{
"error": "invalid role",
})
return
}
} else {
role = constants.DEFAULT_ROLE
roles = strings.Join(constants.DEFAULT_ROLES, ",")
}
uuid := uuid.New()
oauthStateString := uuid.String() + "___" + redirectURL + "___" + role
oauthStateString := uuid.String() + "___" + redirectURL + "___" + roles
provider := c.Param("oauth_provider")

View File

@@ -3,6 +3,7 @@ package handlers
import (
"fmt"
"net/http"
"strings"
"time"
"github.com/authorizerdev/authorizer/server/db"
@@ -50,9 +51,10 @@ func VerifyEmailHandler() gin.HandlerFunc {
db.Mgr.DeleteToken(claim.Email)
userIdStr := fmt.Sprintf("%v", user.ID)
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, user.Roles)
roles := strings.Split(user.Roles, ",")
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, roles)
accessToken, _, _ := utils.CreateAuthToken(user, enum.AccessToken, user.Roles)
accessToken, _, _ := utils.CreateAuthToken(user, enum.AccessToken, roles)
session.SetToken(userIdStr, refreshToken)
utils.SetCookie(c, accessToken)

View File

@@ -7,6 +7,7 @@ import (
"strings"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
@@ -30,7 +31,6 @@ func AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*m
}
user, err := db.Mgr.GetUserByID(params.ID)
if err != nil {
return res, fmt.Errorf(`User not found`)
}
@@ -92,7 +92,7 @@ func AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*m
inputRoles = append(inputRoles, *item)
}
if !utils.IsValidRolesArray(inputRoles) {
if !utils.IsValidRoles(append([]string{}, append(constants.ROLES, constants.PROTECTED_ROLES...)...), inputRoles) {
return res, fmt.Errorf("invalid list of roles")
}
@@ -114,9 +114,8 @@ func AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*m
return res, err
}
userIdStr := fmt.Sprintf("%v", user.ID)
res = &model.User{
ID: userIdStr,
ID: params.ID,
Email: user.Email,
Image: &user.Image,
FirstName: &user.FirstName,

View File

@@ -46,19 +46,19 @@ func Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, e
log.Println("Compare password error:", err)
return res, fmt.Errorf(`invalid password`)
}
role := constants.DEFAULT_ROLE
if params.Role != nil {
// validate role
if !utils.IsValidRole(strings.Split(user.Roles, ","), *params.Role) {
return res, fmt.Errorf(`invalid role`)
roles := constants.DEFAULT_ROLES
currentRoles := strings.Split(user.Roles, ",")
if len(params.Roles) > 0 {
if !utils.IsValidRoles(currentRoles, params.Roles) {
return res, fmt.Errorf(`invalid roles`)
}
role = *params.Role
roles = params.Roles
}
userIdStr := fmt.Sprintf("%v", user.ID)
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, role)
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, roles)
accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, role)
accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, roles)
session.SetToken(userIdStr, refreshToken)

View File

@@ -37,16 +37,15 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
inputRoles := []string{}
if params.Roles != nil && len(params.Roles) > 0 {
if len(params.Roles) > 0 {
// check if roles exists
for _, item := range params.Roles {
inputRoles = append(inputRoles, *item)
}
if !utils.IsValidRolesArray(inputRoles) {
if !utils.IsValidRoles(constants.ROLES, params.Roles) {
return res, fmt.Errorf(`invalid roles`)
} else {
inputRoles = params.Roles
}
} else {
inputRoles = []string{constants.DEFAULT_ROLE}
inputRoles = constants.DEFAULT_ROLES
}
// find user with email
@@ -85,6 +84,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
return res, err
}
userIdStr := fmt.Sprintf("%v", user.ID)
roles := strings.Split(user.Roles, ",")
userToReturn := &model.User{
ID: userIdStr,
Email: user.Email,
@@ -123,9 +123,9 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
}
} else {
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, constants.DEFAULT_ROLE)
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, roles)
accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, constants.DEFAULT_ROLE)
accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, roles)
session.SetToken(userIdStr, refreshToken)
res = &model.AuthResponse{

View File

@@ -14,7 +14,7 @@ import (
"github.com/authorizerdev/authorizer/server/utils"
)
func Token(ctx context.Context, role *string) (*model.AuthResponse, error) {
func Token(ctx context.Context, roles []string) (*model.AuthResponse, error) {
var res *model.AuthResponse
gc, err := utils.GinContextFromContext(ctx)
@@ -30,16 +30,11 @@ func Token(ctx context.Context, role *string) (*model.AuthResponse, error) {
expiresAt := claim["exp"].(int64)
email := fmt.Sprintf("%v", claim["email"])
claimRole := fmt.Sprintf("%v", claim[constants.JWT_ROLE_CLAIM])
user, err := db.Mgr.GetUserByEmail(email)
if err != nil {
return res, err
}
if role != nil && role != &claimRole {
return res, fmt.Errorf(`unauthorized. invalid role for a given token`)
}
userIdStr := fmt.Sprintf("%v", user.ID)
sessionToken := session.GetToken(userIdStr)
@@ -47,15 +42,30 @@ func Token(ctx context.Context, role *string) (*model.AuthResponse, error) {
if sessionToken == "" {
return res, fmt.Errorf(`unauthorized`)
}
// TODO check if refresh/session token has expired
expiresTimeObj := time.Unix(expiresAt, 0)
currentTimeObj := time.Now()
claimRoleInterface := claim[constants.JWT_ROLE_CLAIM].([]interface{})
claimRoles := make([]string, len(claimRoleInterface))
for i, v := range claimRoleInterface {
claimRoles[i] = v.(string)
}
if len(roles) > 0 {
for _, v := range roles {
if !utils.StringContains(claimRoles, v) {
return res, fmt.Errorf(`unauthorized`)
}
}
}
if accessTokenErr != nil || expiresTimeObj.Sub(currentTimeObj).Minutes() <= 5 {
// if access token has expired and refresh/session token is valid
// generate new accessToken
token, expiresAt, _ = utils.CreateAuthToken(user, enum.AccessToken, claimRole)
token, expiresAt, _ = utils.CreateAuthToken(user, enum.AccessToken, claimRoles)
}
utils.SetCookie(gc, token)
res = &model.AuthResponse{
Message: `Token verified`,

View File

@@ -124,31 +124,6 @@ func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model
}()
}
// TODO this idea needs to be verified otherwise every user can make themselves super admin
// rolesToSave := ""
// if params.Roles != nil && len(params.Roles) > 0 {
// currentRoles := strings.Split(user.Roles, ",")
// inputRoles := []string{}
// for _, item := range params.Roles {
// inputRoles = append(inputRoles, *item)
// }
// if !utils.IsValidRolesArray(inputRoles) {
// return res, fmt.Errorf("invalid list of roles")
// }
// if !utils.IsStringArrayEqual(inputRoles, currentRoles) {
// rolesToSave = strings.Join(inputRoles, ",")
// }
// session.DeleteToken(fmt.Sprintf("%v", user.ID))
// utils.DeleteCookie(gc)
// }
// if rolesToSave != "" {
// user.Roles = rolesToSave
// }
_, err = db.Mgr.UpdateUser(user)
if err != nil {
log.Println("Error updating user:", err)

View File

@@ -6,7 +6,6 @@ import (
"strings"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
@@ -43,9 +42,10 @@ func VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.Aut
db.Mgr.DeleteToken(claim.Email)
userIdStr := fmt.Sprintf("%v", user.ID)
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, constants.DEFAULT_ROLE)
roles := strings.Split(user.Roles, ",")
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, roles)
accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, constants.DEFAULT_ROLE)
accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, roles)
session.SetToken(userIdStr, refreshToken)

View File

@@ -26,7 +26,7 @@ type UserAuthClaim struct {
*JWTCustomClaim `json:"authorizer"`
}
func CreateAuthToken(user db.User, tokenType enum.TokenType, role string) (string, int64, error) {
func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (string, int64, error) {
t := jwt.New(jwt.GetSigningMethod(constants.JWT_TYPE))
expiryBound := time.Hour
if tokenType == enum.RefreshToken {
@@ -41,7 +41,7 @@ func CreateAuthToken(user db.User, tokenType enum.TokenType, role string) (strin
"email": user.Email,
"id": user.ID,
"allowed_roles": strings.Split(user.Roles, ","),
constants.JWT_ROLE_CLAIM: role,
constants.JWT_ROLE_CLAIM: roles,
}
t.Claims = &UserAuthClaim{

View File

@@ -18,3 +18,12 @@ func WriteToFile(filename string, data string) error {
}
return file.Sync()
}
func StringContains(s []string, e string) bool {
for _, a := range s {
if a == e {
return true
}
}
return false
}

View File

@@ -16,17 +16,91 @@ func SendVerificationMail(toEmail, token string) error {
Subject := "Please verify your email"
message := fmt.Sprintf(`
<!DOCTYPE HTML PULBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html"; charset=ISO-8859-1">
</head>
<body>
<h1>Please verify your email by clicking on the link below </h1><br/>
<a href="%s">Click here to verify</a>
</body>
</html>
`, constants.AUTHORIZER_URL+"/verify_email"+"?token="+token)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1" name="viewport">
<meta name="x-apple-disable-message-reformatting">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta content="telephone=no" name="format-detection">
<title></title>
<!--[if (mso 16)]>
<style type="text/css">
a {}
</style>
<![endif]-->
<!--[if gte mso 9]><style>sup { font-size: 100%% !important; }</style><![endif]-->
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG></o:AllowPNG>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
</head>
<body style="font-family: sans-serif;">
<div class="es-wrapper-color">
<!--[if gte mso 9]>
<v:background xmlns:v="urn:schemas-microsoft-com:vml" fill="t">
<v:fill type="tile" color="#ffffff"></v:fill>
</v:background>
<![endif]-->
<table class="es-wrapper" width="100%%" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="esd-email-paddings" valign="top">
<table class="es-content esd-footer-popover" cellspacing="0" cellpadding="0" align="center">
<tbody>
<tr>
<td class="esd-stripe" align="center">
<table class="es-content-body" style="border-left:1px solid transparent;border-right:1px solid transparent;border-top:1px solid transparent;border-bottom:1px solid transparent;padding:20px 0px;" width="600" cellspacing="0" cellpadding="0" bgcolor="#ffffff" align="center">
<tbody>
<tr>
<td class="esd-structure es-p20t es-p40b es-p40r es-p40l" esd-custom-block-id="8537" align="left">
<table width="100%%" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="esd-container-frame" width="518" align="left">
<table width="100%%" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank"><img src="%s" alt="icon" style="display: block;" title="icon" width="30"></a></td>
</tr>
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;">
<td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
<p>Hey there 👋</p>
<p>We received a request to sign-up for <b>%s</b>. If this is correct, please confirm your email address by clicking the button below.</p> <br/>
<a href="%s" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Confirm Email</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<div style="position: absolute; left: -9999px; top: -9999px; margin: 0px;"></div>
</body>
</html>
`, constants.ORGANIZATION_LOGO, constants.ORGANIZATION_NAME, constants.AUTHORIZER_URL+"/verify_email"+"?token="+token)
bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
return sender.SendMail(Receiver, Subject, bodyMessage)
@@ -46,17 +120,92 @@ func SendForgotPasswordMail(toEmail, token, host string) error {
Subject := "Reset Password"
message := fmt.Sprintf(`
<!DOCTYPE HTML PULBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html"; charset=ISO-8859-1">
</head>
<body>
<h1>Please use the link below to reset password </h1><br/>
<a href="%s">Reset Password</a>
</body>
</html>
`, constants.RESET_PASSWORD_URL+"?token="+token)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1" name="viewport">
<meta name="x-apple-disable-message-reformatting">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta content="telephone=no" name="format-detection">
<title></title>
<!--[if (mso 16)]>
<style type="text/css">
a {}
</style>
<![endif]-->
<!--[if gte mso 9]><style>sup { font-size: 100%% !important; }</style><![endif]-->
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG></o:AllowPNG>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
</head>
<body style="font-family: sans-serif;">
<div class="es-wrapper-color">
<!--[if gte mso 9]>
<v:background xmlns:v="urn:schemas-microsoft-com:vml" fill="t">
<v:fill type="tile" color="#ffffff"></v:fill>
</v:background>
<![endif]-->
<table class="es-wrapper" width="100%%" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="esd-email-paddings" valign="top">
<table class="es-content esd-footer-popover" cellspacing="0" cellpadding="0" align="center">
<tbody>
<tr>
<td class="esd-stripe" align="center">
<table class="es-content-body" style="border-left:1px solid transparent;border-right:1px solid transparent;border-top:1px solid transparent;border-bottom:1px solid transparent;padding:20px 0px;" width="600" cellspacing="0" cellpadding="0" bgcolor="#ffffff" align="center">
<tbody>
<tr>
<td class="esd-structure es-p20t es-p40b es-p40r es-p40l" esd-custom-block-id="8537" align="left">
<table width="100%%" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="esd-container-frame" width="518" align="left">
<table width="100%%" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank"><img src="%s" alt="icon" style="display: block;" title="icon" width="30"></a></td>
</tr>
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;">
<td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
<p>Hey there 👋</p>
<p>We received a request to reset password for email: <b>%s</b>. If this is correct, please reset the password clicking the button below.</p> <br/>
<a href="%s" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Reset Password</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<div style="position: absolute; left: -9999px; top: -9999px; margin: 0px;"></div>
</body>
</html>
`, constants.ORGANIZATION_LOGO, toEmail, constants.RESET_PASSWORD_URL+"?token="+token)
bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
return sender.SendMail(Receiver, Subject, bodyMessage)

View File

@@ -40,30 +40,14 @@ func IsSuperAdmin(gc *gin.Context) bool {
return secret == constants.ADMIN_SECRET
}
func IsValidRolesArray(roles []string) bool {
func IsValidRoles(userRoles []string, roles []string) bool {
valid := true
currentRoleMap := map[string]bool{}
for _, currentRole := range constants.ROLES {
currentRoleMap[currentRole] = true
}
for _, inputRole := range roles {
if !currentRoleMap[inputRole] {
for _, role := range roles {
if !StringContains(userRoles, role) {
valid = false
break
}
}
return valid
}
func IsValidRole(userRoles []string, role string) bool {
valid := false
for _, currentRole := range userRoles {
if role == currentRole {
valid = true
break
}
}
return valid
}