Compare commits

...

25 Commits

Author SHA1 Message Date
Lakhan Samani
1d61840c6d fix: env decryption + remove log 2022-03-07 15:35:33 +05:30
Lakhan Samani
4b25e8941c fix: env decryption 2022-03-07 15:33:39 +05:30
Lakhan Samani
136eda15bf fix: env encryption 2022-03-07 15:29:37 +05:30
Lakhan Samani
eea6349318 chore: update app version 2022-03-07 12:33:42 +05:30
Lakhan Samani
513b5d2948 fix: env client secret 2022-03-07 12:23:45 +05:30
Lakhan Samani
e61dc2f08a fix: oauth login 2022-03-07 08:31:39 +05:30
Lakhan Samani
07552bc0b1 fix: use url safe code verifier 2022-03-05 13:50:59 +05:30
Lakhan Samani
0787a3b494 feat: add token endpoint 2022-03-04 12:56:11 +05:30
Lakhan Samani
2946428ab8 feat: add userinfo + logout 2022-03-04 00:36:27 +05:30
Lakhan Samani
5c7d32ec16 fix: remove compat cookie 2022-03-03 09:21:48 +05:30
Lakhan Samani
f0f2e0b6c8 fix: auth flow 2022-03-02 17:42:31 +05:30
Lakhan Samani
5399ea8f32 feat: add session token 2022-02-28 21:26:49 +05:30
Lakhan Samani
4830a7e9ac feat: add client secret 2022-02-28 13:14:16 +05:30
Lakhan Samani
df1c56bb1c fix: tests 2022-02-28 07:55:01 +05:30
Lakhan Samani
b68d9ce661 fix: update_env resolver 2022-02-26 20:36:22 +05:30
Lakhan Samani
145091dce1 feat: add well-known jwks.json endpoint 2022-02-26 18:14:43 +05:30
Lakhan Samani
ad46210112 fix: report error on initialization 2022-02-26 10:06:26 +05:30
Lakhan Samani
4e19f73845 fix: segregate env setup 2022-02-26 09:44:55 +05:30
Lakhan Samani
332269ecf9 feat: add well-known config endpoint 2022-02-23 11:24:52 +05:30
Lakhan Samani
dfa96f09a0 feat: add required jwt claims 2022-02-22 11:06:47 +05:30
Lakhan Samani
5bf26f7385 fix: setting custom access token script env 2022-02-18 16:45:12 +05:30
Lakhan Samani
1b269dc6db fix: rename session_client -> redis_client 2022-02-18 09:21:02 +05:30
Lakhan Samani
ce9a115a14 Merge pull request #128 from agarwal-nitesh/feat/redis_cluster_client
Add redis cluster client as a session store.
2022-02-18 09:19:24 +05:30
Nitesh Agarwal
f2f4c72aa6 Add redis cluster client as a session store. 2022-02-17 20:49:54 +05:30
Samyak Bhuta
9970eb16c9 Update README
Minor changes.
2022-02-17 15:04:05 +05:30
106 changed files with 2754 additions and 1335 deletions

3
.gitignore vendored
View File

@@ -10,4 +10,5 @@ build
data.db
.DS_Store
.env.local
*.tar.gz
*.tar.gz
.vscode/

View File

@@ -7,7 +7,7 @@
Authorizer
</h1>
**Authorizer** is an open-source authentication and authorization solution for your applications. Bring your database and have complete control over the user information. You can self-host authorizer instances and connect to any database (Currently supports [Postgres](https://www.postgresql.org/), [MySQL](https://www.mysql.com/), [SQLite](https://www.sqlite.org/index.html), [SQLServer](https://www.microsoft.com/en-us/sql-server/), [MongoDB](https://mongodb.com/),[ArangoDB](https://www.arangodb.com/)).
**Authorizer** is an open-source authentication and authorization solution for your applications. Bring your database and have complete control over the user information. You can self-host authorizer instances and connect to any database (Currently supports [Postgres](https://www.postgresql.org/), [MySQL](https://www.mysql.com/), [SQLite](https://www.sqlite.org/index.html), [SQLServer](https://www.microsoft.com/en-us/sql-server/), [MongoDB](https://mongodb.com/), [ArangoDB](https://www.arangodb.com/)).
## Table of contents

30
app/package-lock.json generated
View File

@@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"@authorizerdev/authorizer-react": "latest",
"@authorizerdev/authorizer-react": "0.7.0",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17",
@@ -24,9 +24,9 @@
}
},
"node_modules/@authorizerdev/authorizer-js": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.2.1.tgz",
"integrity": "sha512-5lQlh+nc5xTsPongfTyCSX24A1WESu/BjhmZwUNuScEOGady0qPoDHE3RBf46dpi5v05wbHCDN1IFEalX5zssQ==",
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.3.0.tgz",
"integrity": "sha512-KCE5Dw5MUnEgstBUayBriDQAOjqbxU7ixC00rTHAE6aD6TxJkeSls0vCTXpvt4iiKhFK6q9BhHwa/5NwWYpDBQ==",
"dependencies": {
"node-fetch": "^2.6.1"
},
@@ -35,11 +35,11 @@
}
},
"node_modules/@authorizerdev/authorizer-react": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.4.3.tgz",
"integrity": "sha512-o/wWe9zZ3ARYdjbDfhGfvOxe1YQrE1YQ+UN9pcq85YSDkbfBkOfcnJ4YxlxWdL0Obd/ErDIeQ3vskyrfvRf3sA==",
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.7.0.tgz",
"integrity": "sha512-cAxUhodftIveSQt+rFuEA0CxjmbpVfE43ioZBwBxqWEuJHPdPH7bohOQRgTyA2xb3QVnh7kr607Tau13DO7qUA==",
"dependencies": {
"@authorizerdev/authorizer-js": "^0.2.1",
"@authorizerdev/authorizer-js": "^0.3.0",
"final-form": "^4.20.2",
"react-final-form": "^6.5.3",
"styled-components": "^5.3.0"
@@ -829,19 +829,19 @@
},
"dependencies": {
"@authorizerdev/authorizer-js": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.2.1.tgz",
"integrity": "sha512-5lQlh+nc5xTsPongfTyCSX24A1WESu/BjhmZwUNuScEOGady0qPoDHE3RBf46dpi5v05wbHCDN1IFEalX5zssQ==",
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.3.0.tgz",
"integrity": "sha512-KCE5Dw5MUnEgstBUayBriDQAOjqbxU7ixC00rTHAE6aD6TxJkeSls0vCTXpvt4iiKhFK6q9BhHwa/5NwWYpDBQ==",
"requires": {
"node-fetch": "^2.6.1"
}
},
"@authorizerdev/authorizer-react": {
"version": "0.4.3",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.4.3.tgz",
"integrity": "sha512-o/wWe9zZ3ARYdjbDfhGfvOxe1YQrE1YQ+UN9pcq85YSDkbfBkOfcnJ4YxlxWdL0Obd/ErDIeQ3vskyrfvRf3sA==",
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.7.0.tgz",
"integrity": "sha512-cAxUhodftIveSQt+rFuEA0CxjmbpVfE43ioZBwBxqWEuJHPdPH7bohOQRgTyA2xb3QVnh7kr607Tau13DO7qUA==",
"requires": {
"@authorizerdev/authorizer-js": "^0.2.1",
"@authorizerdev/authorizer-js": "^0.3.0",
"final-form": "^4.20.2",
"react-final-form": "^6.5.3",
"styled-components": "^5.3.0"

View File

@@ -2,6 +2,7 @@ export const LOGO_URL =
'https://user-images.githubusercontent.com/6964334/147834043-fc384cab-e7ca-40f8-9663-38fc25fd5f3a.png';
export const TextInputType = {
CLIENT_ID: 'CLIENT_ID',
GOOGLE_CLIENT_ID: 'GOOGLE_CLIENT_ID',
GITHUB_CLIENT_ID: 'GITHUB_CLIENT_ID',
FACEBOOK_CLIENT_ID: 'FACEBOOK_CLIENT_ID',
@@ -25,6 +26,7 @@ export const TextInputType = {
};
export const HiddenInputType = {
CLIENT_SECRET: 'CLIENT_SECRET',
GOOGLE_CLIENT_SECRET: 'GOOGLE_CLIENT_SECRET',
GITHUB_CLIENT_SECRET: 'GITHUB_CLIENT_SECRET',
FACEBOOK_CLIENT_SECRET: 'FACEBOOK_CLIENT_SECRET',

View File

@@ -9,6 +9,8 @@ export const AdminSessionQuery = `
export const EnvVariablesQuery = `
query {
_env{
CLIENT_ID,
CLIENT_SECRET,
GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET,
GITHUB_CLIENT_ID,

View File

@@ -240,6 +240,42 @@ export default function Environment() {
return (
<Box m="5" py="5" px="10" bg="white" rounded="md">
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
Your instance information
</Text>
<Stack spacing={6} padding="2% 0%">
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Client ID</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={() => {}}
inputType={TextInputType.CLIENT_ID}
placeholder="Client ID"
isDisabled={true}
/>
</Center>
</Flex>
<Flex>
<Flex w="30%" justifyContent="start" alignItems="center">
<Text fontSize="sm">Client Secret</Text>
</Flex>
<Center w="70%">
<InputField
variables={envVariables}
setVariables={setEnvVariables}
fieldVisibility={fieldVisibility}
setFieldVisibility={setFieldVisibility}
inputType={HiddenInputType.CLIENT_SECRET}
placeholder="Client Secret"
isDisabled={true}
/>
</Center>
</Flex>
</Stack>
<Divider marginTop="2%" marginBottom="2%" />
<Text fontSize="md" paddingTop="2%" fontWeight="bold">
Social Media Logins
</Text>

View File

@@ -59,8 +59,6 @@ const (
EnvKeyAdminCookieName = "ADMIN_COOKIE_NAME"
// EnvKeyResetPasswordURL key for env variable RESET_PASSWORD_URL
EnvKeyResetPasswordURL = "RESET_PASSWORD_URL"
// EnvKeyEncryptionKey key for env variable ENCRYPTION_KEY
EnvKeyEncryptionKey = "ENCRYPTION_KEY"
// EnvKeyDisableEmailVerification key for env variable DISABLE_EMAIL_VERIFICATION
EnvKeyDisableEmailVerification = "DISABLE_EMAIL_VERIFICATION"
// EnvKeyDisableBasicAuthentication key for env variable DISABLE_BASIC_AUTH
@@ -93,8 +91,18 @@ const (
EnvKeyOrganizationName = "ORGANIZATION_NAME"
// EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO
EnvKeyOrganizationLogo = "ORGANIZATION_LOGO"
// EnvKeyIsProd key for env variable IS_PROD
EnvKeyIsProd = "IS_PROD"
// EnvKeyCustomAccessTokenScript key for env variable CUSTOM_ACCESS_TOKEN_SCRIPT
EnvKeyCustomAccessTokenScript = "CUSTOM_ACCESS_TOKEN_SCRIPT"
// Not Exposed Keys
// EnvKeyClientID key for env variable CLIENT_ID
EnvKeyClientID = "CLIENT_ID"
// EnvKeyClientSecret key for env variable CLIENT_SECRET
EnvKeyClientSecret = "CLIENT_SECRET"
// EnvKeyEncryptionKey key for env variable ENCRYPTION_KEY
EnvKeyEncryptionKey = "ENCRYPTION_KEY"
// EnvKeyJWK key for env variable JWK
EnvKeyJWK = "JWK"
// EnvKeyIsProd key for env variable IS_PROD
EnvKeyIsProd = "IS_PROD"
)

View File

@@ -5,4 +5,6 @@ const (
TokenTypeRefreshToken = "refresh_token"
// TokenTypeAccessToken is the access_token token type
TokenTypeAccessToken = "access_token"
// TokenTypeIdentityToken is the identity_token token type
TokenTypeIdentityToken = "id_token"
)

View File

@@ -16,12 +16,12 @@ func SetAdminCookie(gc *gin.Context, token string) {
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), token, 3600, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), token, 3600, "/", host, secure, httpOnly)
}
// GetAdminCookie gets the admin cookie from the request
func GetAdminCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName))
cookie, err := gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName))
if err != nil {
return "", err
}
@@ -42,5 +42,5 @@ func DeleteAdminCookie(gc *gin.Context) {
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), "", -1, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), "", -1, "/", host, secure, httpOnly)
}

View File

@@ -10,13 +10,8 @@ import (
"github.com/gin-gonic/gin"
)
// SetCookie sets the cookie in the response. It sets 4 cookies
// 1 COOKIE_NAME.access_token jwt token for the host (temp.abc.com)
// 2 COOKIE_NAME.access_token.domain jwt token for the domain (abc.com).
// 3 COOKIE_NAME.fingerprint fingerprint hash for the refresh token verification.
// 4 COOKIE_NAME.refresh_token refresh token
// Note all sites don't allow 2nd type of cookie
func SetCookie(gc *gin.Context, accessToken, refreshToken, fingerprintHash string) {
// SetSession sets the session cookie in the response
func SetSession(gc *gin.Context, sessionID string) {
secure := true
httpOnly := true
hostname := utils.GetHost(gc)
@@ -26,77 +21,45 @@ func SetCookie(gc *gin.Context, accessToken, refreshToken, fingerprintHash strin
domain = "." + domain
}
// TODO allow configuring from dashboard
year := 60 * 60 * 24 * 365
thirtyMin := 60 * 30
gc.SetSameSite(http.SameSiteNoneMode)
// set cookie for host
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", accessToken, thirtyMin, "/", host, secure, httpOnly)
// in case of subdomain, set cookie for domain
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", accessToken, thirtyMin, "/", domain, secure, httpOnly)
// set finger print cookie (this should be accessed via cookie only)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", fingerprintHash, year, "/", host, secure, httpOnly)
// set refresh token cookie (this should be accessed via cookie only)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, year, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", sessionID, year, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_domain", sessionID, year, "/", domain, secure, httpOnly)
}
// GetAccessTokenCookie to get access token cookie from the request
func GetAccessTokenCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token")
// DeleteSession sets session cookies to expire
func DeleteSession(gc *gin.Context) {
secure := true
httpOnly := true
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
domain := utils.GetDomainName(hostname)
if domain != "localhost" {
domain = "." + domain
}
gc.SetSameSite(http.SameSiteNoneMode)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", "", -1, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session_domain", "", -1, "/", domain, secure, httpOnly)
}
// GetSession gets the session cookie from context
func GetSession(gc *gin.Context) (string, error) {
var cookie *http.Cookie
var err error
cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + "_session")
if err != nil {
cookie, err = gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token.domain")
cookie, err = gc.Request.Cookie(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + "_session_domain")
if err != nil {
return "", err
}
}
return cookie.Value, nil
}
// GetRefreshTokenCookie to get refresh token cookie
func GetRefreshTokenCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".refresh_token")
decodedValue, err := url.PathUnescape(cookie.Value)
if err != nil {
return "", err
}
return cookie.Value, nil
}
// GetFingerPrintCookie to get fingerprint cookie
func GetFingerPrintCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".fingerprint")
if err != nil {
return "", err
}
// cookie escapes special characters like $
// hence we need to unescape before comparing
decodedValue, err := url.QueryUnescape(cookie.Value)
if err != nil {
return "", err
}
return decodedValue, nil
}
// DeleteCookie sets response cookies to expire
func DeleteCookie(gc *gin.Context) {
secure := true
httpOnly := true
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
domain := utils.GetDomainName(hostname)
if domain != "localhost" {
domain = "." + domain
}
gc.SetSameSite(http.SameSiteNoneMode)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", "", -1, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", "", -1, "/", domain, secure, httpOnly)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", "", -1, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", "", -1, "/", host, secure, httpOnly)
}

View File

@@ -1,35 +1,52 @@
package utils
package crypto
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"encoding/json"
"io"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"golang.org/x/crypto/bcrypt"
)
// EncryptB64 encrypts data into base64 string
func EncryptB64(text string) string {
return base64.StdEncoding.EncodeToString([]byte(text))
}
var bytes = []byte{35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 0o5}
// DecryptB64 decrypts from base64 string to readable string
func DecryptB64(s string) (string, error) {
data, err := base64.StdEncoding.DecodeString(s)
// EncryptAES method is to encrypt or hide any classified text
func EncryptAES(text string) (string, error) {
key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
return string(data), nil
plainText := []byte(text)
cfb := cipher.NewCFBEncrypter(block, bytes)
cipherText := make([]byte, len(plainText))
cfb.XORKeyStream(cipherText, plainText)
return EncryptB64(string(cipherText)), nil
}
// EncryptAES encrypts data using AES algorithm
func EncryptAES(text []byte) ([]byte, error) {
key := []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
// DecryptAES method is to extract back the encrypted text
func DecryptAES(text string) (string, error) {
key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
block, err := aes.NewCipher(key)
if err != nil {
return "", err
}
cipherText, err := DecryptB64(text)
if err != nil {
return "", err
}
cfb := cipher.NewCFBDecrypter(block, bytes)
plainText := make([]byte, len(cipherText))
cfb.XORKeyStream(plainText, []byte(cipherText))
return string(plainText), nil
}
// EncryptAESEnv encrypts data using AES algorithm
// kept for the backward compatibility of env data encryption
func EncryptAESEnv(text []byte) ([]byte, error) {
key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
c, err := aes.NewCipher(key)
var res []byte
if err != nil {
@@ -62,8 +79,9 @@ func EncryptAES(text []byte) ([]byte, error) {
}
// DecryptAES decrypts data using AES algorithm
func DecryptAES(ciphertext []byte) ([]byte, error) {
key := []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
// Kept for the backward compatibility of env data decryption
func DecryptAESEnv(ciphertext []byte) ([]byte, error) {
key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
c, err := aes.NewCipher(key)
var res []byte
if err != nil {
@@ -88,39 +106,3 @@ func DecryptAES(ciphertext []byte) ([]byte, error) {
return plaintext, nil
}
// EncryptEnvData is used to encrypt the env data
func EncryptEnvData(data envstore.Store) (string, error) {
jsonBytes, err := json.Marshal(data)
if err != nil {
return "", err
}
envStoreObj := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
err = json.Unmarshal(jsonBytes, &envStoreObj)
if err != nil {
return "", err
}
configData, err := json.Marshal(envStoreObj)
if err != nil {
return "", err
}
encryptedConfig, err := EncryptAES(configData)
if err != nil {
return "", err
}
return EncryptB64(string(encryptedConfig)), nil
}
// EncryptPassword is used for encrypting password
func EncryptPassword(password string) (string, error) {
pw, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(pw), nil
}

17
server/crypto/b64.go Normal file
View File

@@ -0,0 +1,17 @@
package crypto
import "encoding/base64"
// EncryptB64 encrypts data into base64 string
func EncryptB64(text string) string {
return base64.StdEncoding.EncodeToString([]byte(text))
}
// DecryptB64 decrypts from base64 string to readable string
func DecryptB64(s string) (string, error) {
data, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return "", err
}
return string(data), nil
}

113
server/crypto/common.go Normal file
View File

@@ -0,0 +1,113 @@
package crypto
import (
"crypto/x509"
"encoding/json"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"golang.org/x/crypto/bcrypt"
"gopkg.in/square/go-jose.v2"
)
// GetPubJWK returns JWK for given keys
func GetPubJWK(algo, keyID string, publicKey interface{}) (string, error) {
jwk := &jose.JSONWebKeySet{
Keys: []jose.JSONWebKey{
{
Algorithm: algo,
Key: publicKey,
Use: "sig",
KeyID: keyID,
Certificates: []*x509.Certificate{},
CertificateThumbprintSHA1: []uint8{},
CertificateThumbprintSHA256: []uint8{},
},
},
}
jwkPublicKey, err := jwk.Keys[0].MarshalJSON()
if err != nil {
return "", err
}
return string(jwkPublicKey), nil
}
// GenerateJWKBasedOnEnv generates JWK based on env
// make sure clientID, jwtType, jwtSecret / public & private key pair is set
// this is called while initializing app / when env is updated
func GenerateJWKBasedOnEnv() (string, error) {
jwk := ""
algo := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
clientID := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID)
var err error
// check if jwt secret is provided
if IsHMACA(algo) {
jwk, err = GetPubJWK(algo, clientID, []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret)))
if err != nil {
return "", err
}
}
if IsRSA(algo) {
publicKeyInstance, err := ParseRsaPublicKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey))
if err != nil {
return "", err
}
jwk, err = GetPubJWK(algo, clientID, publicKeyInstance)
if err != nil {
return "", err
}
}
if IsECDSA(algo) {
publicKeyInstance, err := ParseEcdsaPublicKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey))
if err != nil {
return "", err
}
jwk, err = GetPubJWK(algo, clientID, publicKeyInstance)
if err != nil {
return "", err
}
}
return jwk, nil
}
// EncryptEnvData is used to encrypt the env data
func EncryptEnvData(data envstore.Store) (string, error) {
jsonBytes, err := json.Marshal(data)
if err != nil {
return "", err
}
storeData := envstore.EnvStoreObj.GetEnvStoreClone()
err = json.Unmarshal(jsonBytes, &storeData)
if err != nil {
return "", err
}
configData, err := json.Marshal(storeData)
if err != nil {
return "", err
}
encryptedConfig, err := EncryptAESEnv(configData)
if err != nil {
return "", err
}
return string(encryptedConfig), nil
}
// EncryptPassword is used for encrypting password
func EncryptPassword(password string) (string, error) {
pw, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(pw), nil
}

154
server/crypto/ecdsa.go Normal file
View File

@@ -0,0 +1,154 @@
package crypto
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"errors"
)
// NewECDSAKey to generate new ECDSA Key if env is not set
// returns key instance, private key string, public key string, jwk string, error
func NewECDSAKey(algo, keyID string) (*ecdsa.PrivateKey, string, string, string, error) {
var curve elliptic.Curve
switch algo {
case "ES256":
curve = elliptic.P256()
case "ES384":
curve = elliptic.P384()
case "ES512":
curve = elliptic.P521()
default:
return nil, "", "", "", errors.New("Invalid algo")
}
key, err := ecdsa.GenerateKey(curve, rand.Reader)
if err != nil {
return nil, "", "", "", err
}
privateKey, publicKey, err := AsECDSAStr(key, &key.PublicKey)
if err != nil {
return nil, "", "", "", err
}
jwkPublicKey, err := GetPubJWK(algo, keyID, &key.PublicKey)
if err != nil {
return nil, "", "", "", err
}
return key, privateKey, publicKey, string(jwkPublicKey), err
}
// IsECDSA checks if given string is valid ECDSA algo
func IsECDSA(algo string) bool {
switch algo {
case "ES256", "ES384", "ES512":
return true
default:
return false
}
}
// ExportEcdsaPrivateKeyAsPemStr to get ECDSA private key as pem string
func ExportEcdsaPrivateKeyAsPemStr(privkey *ecdsa.PrivateKey) (string, error) {
privkeyBytes, err := x509.MarshalECPrivateKey(privkey)
if err != nil {
return "", err
}
privkeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "ECDSA PRIVATE KEY",
Bytes: privkeyBytes,
},
)
return string(privkeyPem), nil
}
// ExportEcdsaPublicKeyAsPemStr to get ECDSA public key as pem string
func ExportEcdsaPublicKeyAsPemStr(pubkey *ecdsa.PublicKey) (string, error) {
pubkeyBytes, err := x509.MarshalPKIXPublicKey(pubkey)
if err != nil {
return "", err
}
pubkeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "ECDSA PUBLIC KEY",
Bytes: pubkeyBytes,
},
)
return string(pubkeyPem), nil
}
// ParseEcdsaPrivateKeyFromPemStr to parse ECDSA private key from pem string
func ParseEcdsaPrivateKeyFromPemStr(privPEM string) (*ecdsa.PrivateKey, error) {
block, _ := pem.Decode([]byte(privPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
priv, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return priv, nil
}
// ParseEcdsaPublicKeyFromPemStr to parse ECDSA public key from pem string
func ParseEcdsaPublicKeyFromPemStr(pubPEM string) (*ecdsa.PublicKey, error) {
block, _ := pem.Decode([]byte(pubPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
switch pub := pub.(type) {
case *ecdsa.PublicKey:
return pub, nil
default:
break // fall through
}
return nil, errors.New("Key type is not ECDSA")
}
// AsECDSAStr returns private, public key string or error
func AsECDSAStr(privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey) (string, string, error) {
// Export the keys to pem string
privPem, err := ExportEcdsaPrivateKeyAsPemStr(privateKey)
if err != nil {
return "", "", err
}
pubPem, err := ExportEcdsaPublicKeyAsPemStr(publicKey)
if err != nil {
return "", "", err
}
// Import the keys from pem string
privParsed, err := ParseEcdsaPrivateKeyFromPemStr(privPem)
if err != nil {
return "", "", err
}
pubParsed, err := ParseEcdsaPublicKeyFromPemStr(pubPem)
if err != nil {
return "", "", err
}
// Export the newly imported keys
privParsedPem, err := ExportEcdsaPrivateKeyAsPemStr(privParsed)
if err != nil {
return "", "", err
}
pubParsedPem, err := ExportEcdsaPublicKeyAsPemStr(pubParsed)
if err != nil {
return "", "", err
}
return privParsedPem, pubParsedPem, nil
}

26
server/crypto/hmac.go Normal file
View File

@@ -0,0 +1,26 @@
package crypto
import (
"github.com/google/uuid"
)
// NewHMAC key returns new key that can be used to ecnrypt data using HMAC algo
// returns key, string, error
func NewHMACKey(algo, keyID string) (string, string, error) {
key := uuid.New().String()
jwkPublicKey, err := GetPubJWK(algo, keyID, []byte(key))
if err != nil {
return "", "", err
}
return key, string(jwkPublicKey), nil
}
// IsHMACValid checks if given string is valid HMCA algo
func IsHMACA(algo string) bool {
switch algo {
case "HS256", "HS384", "HS512":
return true
default:
return false
}
}

118
server/crypto/rsa.go Normal file
View File

@@ -0,0 +1,118 @@
package crypto
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"errors"
)
// NewRSAKey to generate new RSA Key if env is not set
// returns key instance, private key string, public key string, jwk string, error
func NewRSAKey(algo, keyID string) (*rsa.PrivateKey, string, string, string, error) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, "", "", "", err
}
privateKey, publicKey, err := AsRSAStr(key, &key.PublicKey)
if err != nil {
return nil, "", "", "", err
}
jwkPublicKey, err := GetPubJWK(algo, keyID, &key.PublicKey)
if err != nil {
return nil, "", "", "", err
}
return key, privateKey, publicKey, string(jwkPublicKey), err
}
// IsRSA checks if given string is valid RSA algo
func IsRSA(algo string) bool {
switch algo {
case "RS256", "RS384", "RS512":
return true
default:
return false
}
}
// ExportRsaPrivateKeyAsPemStr to get RSA private key as pem string
func ExportRsaPrivateKeyAsPemStr(privkey *rsa.PrivateKey) string {
privkeyBytes := x509.MarshalPKCS1PrivateKey(privkey)
privkeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privkeyBytes,
},
)
return string(privkeyPem)
}
// ExportRsaPublicKeyAsPemStr to get RSA public key as pem string
func ExportRsaPublicKeyAsPemStr(pubkey *rsa.PublicKey) string {
pubkeyBytes := x509.MarshalPKCS1PublicKey(pubkey)
pubkeyPem := pem.EncodeToMemory(
&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: pubkeyBytes,
},
)
return string(pubkeyPem)
}
// ParseRsaPrivateKeyFromPemStr to parse RSA private key from pem string
func ParseRsaPrivateKeyFromPemStr(privPEM string) (*rsa.PrivateKey, error) {
block, _ := pem.Decode([]byte(privPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return priv, nil
}
// ParseRsaPublicKeyFromPemStr to parse RSA public key from pem string
func ParseRsaPublicKeyFromPemStr(pubPEM string) (*rsa.PublicKey, error) {
block, _ := pem.Decode([]byte(pubPEM))
if block == nil {
return nil, errors.New("failed to parse PEM block containing the key")
}
pub, err := x509.ParsePKCS1PublicKey(block.Bytes)
if err != nil {
return nil, err
}
return pub, nil
}
// AsRSAStr returns private, public key string or error
func AsRSAStr(privateKey *rsa.PrivateKey, publickKey *rsa.PublicKey) (string, string, error) {
// Export the keys to pem string
privPem := ExportRsaPrivateKeyAsPemStr(privateKey)
pubPem := ExportRsaPublicKeyAsPemStr(publickKey)
// Import the keys from pem string
privParsed, err := ParseRsaPrivateKeyFromPemStr(privPem)
if err != nil {
return "", "", err
}
pubParsed, err := ParseRsaPublicKeyFromPemStr(pubPem)
if err != nil {
return "", "", err
}
// Export the newly imported keys
privParsedPem := ExportRsaPrivateKeyAsPemStr(privParsed)
pubParsedPem := ExportRsaPublicKeyAsPemStr(pubParsed)
return privParsedPem, pubParsedPem, nil
}

View File

@@ -1,8 +1,6 @@
package db
import (
"log"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/providers"
"github.com/authorizerdev/authorizer/server/db/providers/arangodb"
@@ -14,31 +12,33 @@ import (
// Provider returns the current database provider
var Provider providers.Provider
func InitDB() {
func InitDB() error {
var err error
isSQL := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeArangodb && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeMongodb
isArangoDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeArangodb
isMongoDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeMongodb
isSQL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeArangodb && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeMongodb
isArangoDB := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeArangodb
isMongoDB := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeMongodb
if isSQL {
Provider, err = sql.NewProvider()
if err != nil {
log.Fatal("=> error setting sql provider:", err)
return err
}
}
if isArangoDB {
Provider, err = arangodb.NewProvider()
if err != nil {
log.Fatal("=> error setting arangodb provider:", err)
return err
}
}
if isMongoDB {
Provider, err = mongodb.NewProvider()
if err != nil {
log.Fatal("=> error setting arangodb provider:", err)
return err
}
}
return nil
}

View File

@@ -12,6 +12,7 @@ type VerificationRequest struct {
CreatedAt int64 `json:"created_at" bson:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email"`
Nonce string `gorm:"type:char(36)" json:"nonce" bson:"nonce"`
}
func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest {

View File

@@ -24,7 +24,7 @@ type provider struct {
func NewProvider() (*provider, error) {
ctx := context.Background()
conn, err := http.NewConnection(http.ConnectionConfig{
Endpoints: []string{envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)},
Endpoints: []string{envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)},
})
if err != nil {
return nil, err
@@ -39,16 +39,16 @@ func NewProvider() (*provider, error) {
var arangodb driver.Database
arangodb_exists, err := arangoClient.DatabaseExists(nil, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName))
arangodb_exists, err := arangoClient.DatabaseExists(nil, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName))
if arangodb_exists {
log.Println(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName) + " db exists already")
arangodb, err = arangoClient.Database(nil, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName))
log.Println(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName) + " db exists already")
arangodb, err = arangoClient.Database(nil, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName))
if err != nil {
return nil, err
}
} else {
arangodb, err = arangoClient.CreateDatabase(nil, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), nil)
arangodb, err = arangoClient.CreateDatabase(nil, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), nil)
if err != nil {
return nil, err
}

View File

@@ -34,7 +34,6 @@ func (p *provider) DeleteSession(userId string) error {
}
cursor, err := p.db.Query(nil, query, bindVars)
if err != nil {
log.Println("=> error deleting arangodb session:", err)
return err
}
defer cursor.Close()

View File

@@ -23,7 +23,7 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
}
if user.Roles == "" {
user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
user.Roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
}
user.CreatedAt = time.Now().Unix()

View File

@@ -19,7 +19,7 @@ type provider struct {
// NewProvider to initialize mongodb connection
func NewProvider() (*provider, error) {
mongodbOptions := options.Client().ApplyURI(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL))
mongodbOptions := options.Client().ApplyURI(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL))
maxWait := time.Duration(5 * time.Second)
mongodbOptions.ConnectTimeout = &maxWait
mongoClient, err := mongo.NewClient(mongodbOptions)
@@ -37,7 +37,7 @@ func NewProvider() (*provider, error) {
return nil, err
}
mongodb := mongoClient.Database(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), options.Database())
mongodb := mongoClient.Database(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), options.Database())
mongodb.CreateCollection(ctx, models.Collections.User, options.CreateCollection())
userCollection := mongodb.Collection(models.Collections.User, options.Collection())

View File

@@ -21,7 +21,7 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
}
if user.Roles == "" {
user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
user.Roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
}
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()

View File

@@ -41,22 +41,25 @@ func NewProvider() (*provider, error) {
TablePrefix: models.Prefix,
},
}
switch envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) {
switch envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) {
case constants.DbTypePostgres, constants.DbTypeYugabyte:
sqlDB, err = gorm.Open(postgres.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
sqlDB, err = gorm.Open(postgres.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
case constants.DbTypeSqlite:
sqlDB, err = gorm.Open(sqlite.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
sqlDB, err = gorm.Open(sqlite.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
case constants.DbTypeMysql, constants.DbTypeMariaDB:
sqlDB, err = gorm.Open(mysql.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
sqlDB, err = gorm.Open(mysql.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
case constants.DbTypeSqlserver:
sqlDB, err = gorm.Open(sqlserver.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
sqlDB, err = gorm.Open(sqlserver.Open(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
}
if err != nil {
return nil, err
}
sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{})
err = sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{})
if err != nil {
return nil, err
}
return &provider{
db: sqlDB,
}, nil

View File

@@ -20,7 +20,7 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
}
if user.Roles == "" {
user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
user.Roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
}
user.CreatedAt = time.Now().Unix()

View File

@@ -31,14 +31,18 @@ func addEmailTemplate(a string, b map[string]interface{}, templateName string) s
// SendMail function to send mail
func SendMail(to []string, Subject, bodyMessage string) error {
// dont trigger email sending in case of test
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnv) == "test" {
return nil
}
m := gomail.NewMessage()
m.SetHeader("From", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySenderEmail))
m.SetHeader("From", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySenderEmail))
m.SetHeader("To", to...)
m.SetHeader("Subject", Subject)
m.SetBody("text/html", bodyMessage)
port, _ := strconv.Atoi(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPort))
d := gomail.NewDialer(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpHost), port, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpUsername), envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPassword))
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnv) == "development" {
port, _ := strconv.Atoi(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPort))
d := gomail.NewDialer(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpHost), port, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpUsername), envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPassword))
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnv) == "development" {
d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
}
if err := d.DialAndSend(m); err != nil {

View File

@@ -7,9 +7,9 @@ import (
// SendForgotPasswordMail to send forgot password email
func SendForgotPasswordMail(toEmail, token, hostname string) error {
resetPasswordUrl := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL)
resetPasswordUrl := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL)
if resetPasswordUrl == "" {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyResetPasswordURL, hostname+"/app/reset-password")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyResetPasswordURL, hostname+"/app/reset-password")
}
// The receiver needs to be in slice as the receive supports multiple receiver
@@ -103,8 +103,8 @@ func SendForgotPasswordMail(toEmail, token, hostname string) error {
`
data := make(map[string]interface{}, 3)
data["org_logo"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
data["org_name"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
data["org_logo"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
data["org_name"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
data["verification_url"] = resetPasswordUrl + "?token=" + token
message = addEmailTemplate(message, data, "reset_password_email.tmpl")

View File

@@ -97,8 +97,8 @@ func SendVerificationMail(toEmail, token, hostname string) error {
</html>
`
data := make(map[string]interface{}, 3)
data["org_logo"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
data["org_name"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
data["org_logo"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
data["org_name"] = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
data["verification_url"] = hostname + "/verify_email?token=" + token
message = addEmailTemplate(message, data, "verify_email.tmpl")
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)

230
server/env/env.go vendored
View File

@@ -1,11 +1,13 @@
package env
import (
"errors"
"log"
"os"
"strings"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin"
@@ -13,10 +15,85 @@ import (
"github.com/joho/godotenv"
)
// InitRequiredEnv to initialize EnvData and through error if required env are not present
func InitRequiredEnv() error {
envPath := os.Getenv(constants.EnvKeyEnvPath)
if envPath == "" {
envPath = envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnvPath)
if envPath == "" {
envPath = `.env`
}
}
if envstore.ARG_ENV_FILE != nil && *envstore.ARG_ENV_FILE != "" {
envPath = *envstore.ARG_ENV_FILE
}
err := godotenv.Load(envPath)
if err != nil {
log.Printf("using OS env instead of %s file", envPath)
}
dbURL := os.Getenv(constants.EnvKeyDatabaseURL)
dbType := os.Getenv(constants.EnvKeyDatabaseType)
dbName := os.Getenv(constants.EnvKeyDatabaseName)
if strings.TrimSpace(dbType) == "" {
if envstore.ARG_DB_TYPE != nil && *envstore.ARG_DB_TYPE != "" {
dbType = strings.TrimSpace(*envstore.ARG_DB_TYPE)
}
if dbType == "" {
return errors.New("invalid database type. DATABASE_TYPE is empty")
}
}
if strings.TrimSpace(dbURL) == "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL) == "" {
if envstore.ARG_DB_URL != nil && *envstore.ARG_DB_URL != "" {
dbURL = strings.TrimSpace(*envstore.ARG_DB_URL)
}
if dbURL == "" {
return errors.New("invalid database url. DATABASE_URL is required")
}
}
if dbName == "" {
if dbName == "" {
dbName = "authorizer"
}
}
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, envPath)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseName, dbName)
return nil
}
// InitEnv to initialize EnvData and through error if required env are not present
func InitEnv() {
// get clone of current store
envData := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
func InitAllEnv() error {
envData, err := GetEnvData()
if err != nil {
log.Println("No env data found in db, using local clone of env data")
// get clone of current store
envData = envstore.EnvStoreObj.GetEnvStoreClone()
}
clientID := envData.StringEnv[constants.EnvKeyClientID]
// unique client id for each instance
if clientID == "" {
clientID = uuid.New().String()
envData.StringEnv[constants.EnvKeyClientID] = clientID
}
clientSecret := envData.StringEnv[constants.EnvKeyClientSecret]
// unique client id for each instance
if clientSecret == "" {
clientSecret = uuid.New().String()
envData.StringEnv[constants.EnvKeyClientSecret] = clientSecret
}
if envData.StringEnv[constants.EnvKeyEnv] == "" {
envData.StringEnv[constants.EnvKeyEnv] = os.Getenv(constants.EnvKeyEnv)
@@ -36,19 +113,6 @@ func InitEnv() {
envData.StringEnv[constants.EnvKeyAppURL] = os.Getenv(constants.EnvKeyAppURL)
}
if envData.StringEnv[constants.EnvKeyEnvPath] == "" {
envData.StringEnv[constants.EnvKeyEnvPath] = `.env`
}
if envstore.ARG_ENV_FILE != nil && *envstore.ARG_ENV_FILE != "" {
envData.StringEnv[constants.EnvKeyEnvPath] = *envstore.ARG_ENV_FILE
}
err := godotenv.Load(envData.StringEnv[constants.EnvKeyEnvPath])
if err != nil {
log.Printf("using OS env instead of %s file", envData.StringEnv[constants.EnvKeyEnvPath])
}
if envData.StringEnv[constants.EnvKeyPort] == "" {
envData.StringEnv[constants.EnvKeyPort] = os.Getenv(constants.EnvKeyPort)
if envData.StringEnv[constants.EnvKeyPort] == "" {
@@ -60,37 +124,6 @@ func InitEnv() {
envData.StringEnv[constants.EnvKeyAdminSecret] = os.Getenv(constants.EnvKeyAdminSecret)
}
if envData.StringEnv[constants.EnvKeyDatabaseType] == "" {
envData.StringEnv[constants.EnvKeyDatabaseType] = os.Getenv(constants.EnvKeyDatabaseType)
if envstore.ARG_DB_TYPE != nil && *envstore.ARG_DB_TYPE != "" {
envData.StringEnv[constants.EnvKeyDatabaseType] = *envstore.ARG_DB_TYPE
}
if envData.StringEnv[constants.EnvKeyDatabaseType] == "" {
panic("DATABASE_TYPE is required")
}
}
if envData.StringEnv[constants.EnvKeyDatabaseURL] == "" {
envData.StringEnv[constants.EnvKeyDatabaseURL] = os.Getenv(constants.EnvKeyDatabaseURL)
if envstore.ARG_DB_URL != nil && *envstore.ARG_DB_URL != "" {
envData.StringEnv[constants.EnvKeyDatabaseURL] = *envstore.ARG_DB_URL
}
if envData.StringEnv[constants.EnvKeyDatabaseURL] == "" {
panic("DATABASE_URL is required")
}
}
if envData.StringEnv[constants.EnvKeyDatabaseName] == "" {
envData.StringEnv[constants.EnvKeyDatabaseName] = os.Getenv(constants.EnvKeyDatabaseName)
if envData.StringEnv[constants.EnvKeyDatabaseName] == "" {
envData.StringEnv[constants.EnvKeyDatabaseName] = "authorizer"
}
}
if envData.StringEnv[constants.EnvKeySmtpHost] == "" {
envData.StringEnv[constants.EnvKeySmtpHost] = os.Getenv(constants.EnvKeySmtpHost)
}
@@ -111,28 +144,88 @@ func InitEnv() {
envData.StringEnv[constants.EnvKeySenderEmail] = os.Getenv(constants.EnvKeySenderEmail)
}
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" {
envData.StringEnv[constants.EnvKeyJwtSecret] = os.Getenv(constants.EnvKeyJwtSecret)
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" {
envData.StringEnv[constants.EnvKeyJwtSecret] = uuid.New().String()
}
}
if envData.StringEnv[constants.EnvKeyJwtPrivateKey] == "" {
envData.StringEnv[constants.EnvKeyJwtPrivateKey] = os.Getenv(constants.EnvKeyJwtPrivateKey)
}
if envData.StringEnv[constants.EnvKeyJwtPublicKey] == "" {
envData.StringEnv[constants.EnvKeyJwtPublicKey] = os.Getenv(constants.EnvKeyJwtPublicKey)
}
if envData.StringEnv[constants.EnvKeyJwtType] == "" {
algo := envData.StringEnv[constants.EnvKeyJwtType]
if algo == "" {
envData.StringEnv[constants.EnvKeyJwtType] = os.Getenv(constants.EnvKeyJwtType)
if envData.StringEnv[constants.EnvKeyJwtType] == "" {
envData.StringEnv[constants.EnvKeyJwtType] = "HS256"
envData.StringEnv[constants.EnvKeyJwtType] = "RS256"
algo = envData.StringEnv[constants.EnvKeyJwtType]
} else {
algo = envData.StringEnv[constants.EnvKeyJwtType]
if !crypto.IsHMACA(algo) && !crypto.IsRSA(algo) && !crypto.IsECDSA(algo) {
return errors.New("invalid JWT_TYPE")
}
}
}
if crypto.IsHMACA(algo) {
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" {
envData.StringEnv[constants.EnvKeyJwtSecret] = os.Getenv(constants.EnvKeyJwtSecret)
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" {
envData.StringEnv[constants.EnvKeyJwtSecret], _, err = crypto.NewHMACKey(algo, clientID)
if err != nil {
return err
}
}
}
}
if crypto.IsRSA(algo) || crypto.IsECDSA(algo) {
privateKey, publicKey := "", ""
if envData.StringEnv[constants.EnvKeyJwtPrivateKey] == "" {
privateKey = os.Getenv(constants.EnvKeyJwtPrivateKey)
}
if envData.StringEnv[constants.EnvKeyJwtPublicKey] == "" {
publicKey = os.Getenv(constants.EnvKeyJwtPublicKey)
}
// if algo is RSA / ECDSA, then we need to have both private and public key
// if either of them is not present generate new keys
if privateKey == "" || publicKey == "" {
if crypto.IsRSA(algo) {
_, privateKey, publicKey, _, err = crypto.NewRSAKey(algo, clientID)
if err != nil {
return err
}
} else if crypto.IsECDSA(algo) {
_, privateKey, publicKey, _, err = crypto.NewECDSAKey(algo, clientID)
if err != nil {
return err
}
}
} else {
// parse keys to make sure they are valid
if crypto.IsRSA(algo) {
_, err = crypto.ParseRsaPrivateKeyFromPemStr(privateKey)
if err != nil {
return err
}
_, err := crypto.ParseRsaPublicKeyFromPemStr(publicKey)
if err != nil {
return err
}
} else if crypto.IsECDSA(algo) {
_, err = crypto.ParseEcdsaPrivateKeyFromPemStr(privateKey)
if err != nil {
return err
}
_, err := crypto.ParseEcdsaPublicKeyFromPemStr(publicKey)
if err != nil {
return err
}
}
}
envData.StringEnv[constants.EnvKeyJwtPrivateKey] = privateKey
envData.StringEnv[constants.EnvKeyJwtPublicKey] = publicKey
}
if envData.StringEnv[constants.EnvKeyJwtRoleClaim] == "" {
envData.StringEnv[constants.EnvKeyJwtRoleClaim] = os.Getenv(constants.EnvKeyJwtRoleClaim)
@@ -141,6 +234,10 @@ func InitEnv() {
}
}
if envData.StringEnv[constants.EnvKeyCustomAccessTokenScript] == "" {
envData.StringEnv[constants.EnvKeyCustomAccessTokenScript] = os.Getenv(constants.EnvKeyCustomAccessTokenScript)
}
if envData.StringEnv[constants.EnvKeyRedisURL] == "" {
envData.StringEnv[constants.EnvKeyRedisURL] = os.Getenv(constants.EnvKeyRedisURL)
}
@@ -260,7 +357,7 @@ func InitEnv() {
}
if len(roles) > 0 && len(defaultRoles) == 0 && len(defaultRolesEnv) > 0 {
panic(`Invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`)
return errors.New(`invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`)
}
envData.SliceEnv[constants.EnvKeyRoles] = roles
@@ -275,5 +372,6 @@ func InitEnv() {
envData.StringEnv[constants.EnvKeyOrganizationLogo] = os.Getenv(constants.EnvKeyOrganizationLogo)
}
envstore.EnvInMemoryStoreObj.UpdateEnvStore(envData)
envstore.EnvStoreObj.UpdateEnvStore(envData)
return nil
}

View File

@@ -7,14 +7,51 @@ import (
"strconv"
"strings"
"github.com/google/uuid"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/google/uuid"
)
// GetEnvData returns the env data from database
func GetEnvData() (envstore.Store, error) {
var result envstore.Store
env, err := db.Provider.GetEnv()
// config not found in db
if err != nil {
return result, err
}
encryptionKey := env.Hash
decryptedEncryptionKey, err := crypto.DecryptB64(encryptionKey)
if err != nil {
return result, err
}
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData)
if err != nil {
return result, err
}
decryptedConfigs, err := crypto.DecryptAESEnv([]byte(b64DecryptedConfig))
if err != nil {
return result, err
}
err = json.Unmarshal(decryptedConfigs, &result)
if err != nil {
return result, err
}
return result, err
}
// PersistEnv persists the environment variables to the database
func PersistEnv() error {
env, err := db.Provider.GetEnv()
@@ -22,45 +59,40 @@ func PersistEnv() error {
if err != nil {
// AES encryption needs 32 bit key only, so we chop off last 4 characters from 36 bit uuid
hash := uuid.New().String()[:36-4]
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, hash)
encodedHash := utils.EncryptB64(hash)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, hash)
encodedHash := crypto.EncryptB64(hash)
encryptedConfig, err := utils.EncryptEnvData(envstore.EnvInMemoryStoreObj.GetEnvStoreClone())
encryptedConfig, err := crypto.EncryptEnvData(envstore.EnvStoreObj.GetEnvStoreClone())
if err != nil {
return err
}
// configData, err := json.Marshal()
// if err != nil {
// return err
// }
// encryptedConfig, err := utils.EncryptAES(configData)
// if err != nil {
// return err
// }
env = models.Env{
Hash: encodedHash,
EnvData: encryptedConfig,
}
db.Provider.AddEnv(env)
env, err = db.Provider.AddEnv(env)
if err != nil {
return err
}
} else {
// decrypt the config data from db
// decryption can be done using the hash stored in db
encryptionKey := env.Hash
decryptedEncryptionKey, err := utils.DecryptB64(encryptionKey)
decryptedEncryptionKey, err := crypto.DecryptB64(encryptionKey)
if err != nil {
return err
}
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
b64DecryptedConfig, err := utils.DecryptB64(env.EnvData)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData)
if err != nil {
return err
}
decryptedConfigs, err := utils.DecryptAES([]byte(b64DecryptedConfig))
decryptedConfigs, err := crypto.DecryptAESEnv([]byte(b64DecryptedConfig))
if err != nil {
return err
}
@@ -79,7 +111,8 @@ func PersistEnv() error {
hasChanged := false
for key, value := range storeData.StringEnv {
if key != constants.EnvKeyEncryptionKey {
// don't override unexposed envs
if key != constants.EnvKeyEncryptionKey && key != constants.EnvKeyClientID && key != constants.EnvKeyClientSecret && key != constants.EnvKeyJWK {
// check only for derivative keys
// No need to check for ENCRYPTION_KEY which special key we use for encrypting config data
// as we have removed it from json
@@ -132,10 +165,16 @@ func PersistEnv() error {
hasChanged = true
}
}
envstore.EnvStoreObj.UpdateEnvStore(storeData)
jwk, err := crypto.GenerateJWKBasedOnEnv()
if err != nil {
return err
}
// updating jwk
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJWK, jwk)
envstore.EnvInMemoryStoreObj.UpdateEnvStore(storeData)
if hasChanged {
encryptedConfig, err := utils.EncryptEnvData(storeData)
encryptedConfig, err := crypto.EncryptEnvData(storeData)
if err != nil {
return err
}
@@ -147,7 +186,6 @@ func PersistEnv() error {
return err
}
}
}
return nil

View File

@@ -22,14 +22,13 @@ type Store struct {
SliceEnv map[string][]string `json:"slice_env"`
}
// EnvInMemoryStore struct
type EnvInMemoryStore struct {
// EnvStore struct
type EnvStore struct {
mutex sync.Mutex
store *Store
}
// EnvInMemoryStoreObj global variable for EnvInMemoryStore
var EnvInMemoryStoreObj = &EnvInMemoryStore{
var defaultStore = &EnvStore{
store: &Store{
StringEnv: map[string]string{
constants.EnvKeyAdminCookieName: "authorizer-admin",
@@ -47,8 +46,11 @@ var EnvInMemoryStoreObj = &EnvInMemoryStore{
},
}
// EnvStoreObj.GetBoolStoreEnvVariable global variable for EnvStore
var EnvStoreObj = defaultStore
// UpdateEnvStore to update the whole env store object
func (e *EnvInMemoryStore) UpdateEnvStore(store Store) {
func (e *EnvStore) UpdateEnvStore(store Store) {
e.mutex.Lock()
defer e.mutex.Unlock()
// just override the keys + new keys
@@ -67,7 +69,7 @@ func (e *EnvInMemoryStore) UpdateEnvStore(store Store) {
}
// UpdateEnvVariable to update the particular env variable
func (e *EnvInMemoryStore) UpdateEnvVariable(storeIdentifier, key string, value interface{}) {
func (e *EnvStore) UpdateEnvVariable(storeIdentifier, key string, value interface{}) {
e.mutex.Lock()
defer e.mutex.Unlock()
switch storeIdentifier {
@@ -81,31 +83,37 @@ func (e *EnvInMemoryStore) UpdateEnvVariable(storeIdentifier, key string, value
}
// GetStringStoreEnvVariable to get the env variable from string store object
func (e *EnvInMemoryStore) GetStringStoreEnvVariable(key string) string {
func (e *EnvStore) GetStringStoreEnvVariable(key string) string {
// e.mutex.Lock()
// defer e.mutex.Unlock()
return e.store.StringEnv[key]
}
// GetBoolStoreEnvVariable to get the env variable from bool store object
func (e *EnvInMemoryStore) GetBoolStoreEnvVariable(key string) bool {
func (e *EnvStore) GetBoolStoreEnvVariable(key string) bool {
// e.mutex.Lock()
// defer e.mutex.Unlock()
return e.store.BoolEnv[key]
}
// GetSliceStoreEnvVariable to get the env variable from slice store object
func (e *EnvInMemoryStore) GetSliceStoreEnvVariable(key string) []string {
func (e *EnvStore) GetSliceStoreEnvVariable(key string) []string {
// e.mutex.Lock()
// defer e.mutex.Unlock()
return e.store.SliceEnv[key]
}
// GetEnvStoreClone to get clone of current env store object
func (e *EnvInMemoryStore) GetEnvStoreClone() Store {
func (e *EnvStore) GetEnvStoreClone() Store {
e.mutex.Lock()
defer e.mutex.Unlock()
result := *e.store
return result
}
func (e *EnvStore) ResetStore() {
e.mutex.Lock()
defer e.mutex.Unlock()
e.store = defaultStore.store
}

View File

@@ -30,6 +30,7 @@ require (
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/mail.v2 v2.3.1
gopkg.in/square/go-jose.v2 v2.6.0
gopkg.in/yaml.v2 v2.4.0 // indirect
gorm.io/driver/mysql v1.2.1
gorm.io/driver/postgres v1.2.3

View File

@@ -682,8 +682,9 @@ gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbRxng+XOj5ITVgY24WcbNnQopyfKoYQ=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@@ -44,16 +44,20 @@ type DirectiveRoot struct {
type ComplexityRoot struct {
AuthResponse struct {
AccessToken func(childComplexity int) int
ExpiresAt func(childComplexity int) int
Message func(childComplexity int) int
User func(childComplexity int) int
AccessToken func(childComplexity int) int
ExpiresIn func(childComplexity int) int
IDToken func(childComplexity int) int
Message func(childComplexity int) int
RefreshToken func(childComplexity int) int
User func(childComplexity int) int
}
Env struct {
AdminSecret func(childComplexity int) int
AllowedOrigins func(childComplexity int) int
AppURL func(childComplexity int) int
ClientID func(childComplexity int) int
ClientSecret func(childComplexity int) int
CookieName func(childComplexity int) int
CustomAccessTokenScript func(childComplexity int) int
DatabaseName func(childComplexity int) int
@@ -94,6 +98,7 @@ type ComplexityRoot struct {
}
Meta struct {
ClientID func(childComplexity int) int
IsBasicAuthenticationEnabled func(childComplexity int) int
IsEmailVerificationEnabled func(childComplexity int) int
IsFacebookLoginEnabled func(childComplexity int) int
@@ -131,7 +136,6 @@ type ComplexityRoot struct {
Query struct {
AdminSession func(childComplexity int) int
Env func(childComplexity int) int
IsValidJwt func(childComplexity int, params *model.IsValidJWTQueryInput) int
Meta func(childComplexity int) int
Profile func(childComplexity int) int
Session func(childComplexity int, params *model.SessionQueryInput) int
@@ -168,11 +172,6 @@ type ComplexityRoot struct {
Users func(childComplexity int) int
}
ValidJWTResponse struct {
Message func(childComplexity int) int
Valid func(childComplexity int) int
}
VerificationRequest struct {
CreatedAt func(childComplexity int) int
Email func(childComplexity int) int
@@ -209,7 +208,6 @@ type MutationResolver interface {
type QueryResolver interface {
Meta(ctx context.Context) (*model.Meta, error)
Session(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error)
IsValidJwt(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error)
Profile(ctx context.Context) (*model.User, error)
Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error)
VerificationRequests(ctx context.Context, params *model.PaginatedInput) (*model.VerificationRequests, error)
@@ -239,12 +237,19 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.AuthResponse.AccessToken(childComplexity), true
case "AuthResponse.expires_at":
if e.complexity.AuthResponse.ExpiresAt == nil {
case "AuthResponse.expires_in":
if e.complexity.AuthResponse.ExpiresIn == nil {
break
}
return e.complexity.AuthResponse.ExpiresAt(childComplexity), true
return e.complexity.AuthResponse.ExpiresIn(childComplexity), true
case "AuthResponse.id_token":
if e.complexity.AuthResponse.IDToken == nil {
break
}
return e.complexity.AuthResponse.IDToken(childComplexity), true
case "AuthResponse.message":
if e.complexity.AuthResponse.Message == nil {
@@ -253,6 +258,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.AuthResponse.Message(childComplexity), true
case "AuthResponse.refresh_token":
if e.complexity.AuthResponse.RefreshToken == nil {
break
}
return e.complexity.AuthResponse.RefreshToken(childComplexity), true
case "AuthResponse.user":
if e.complexity.AuthResponse.User == nil {
break
@@ -281,6 +293,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Env.AppURL(childComplexity), true
case "Env.CLIENT_ID":
if e.complexity.Env.ClientID == nil {
break
}
return e.complexity.Env.ClientID(childComplexity), true
case "Env.CLIENT_SECRET":
if e.complexity.Env.ClientSecret == nil {
break
}
return e.complexity.Env.ClientSecret(childComplexity), true
case "Env.COOKIE_NAME":
if e.complexity.Env.CookieName == nil {
break
@@ -519,6 +545,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Error.Reason(childComplexity), true
case "Meta.client_id":
if e.complexity.Meta.ClientID == nil {
break
}
return e.complexity.Meta.ClientID(childComplexity), true
case "Meta.is_basic_authentication_enabled":
if e.complexity.Meta.IsBasicAuthenticationEnabled == nil {
break
@@ -780,18 +813,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Query.Env(childComplexity), true
case "Query.is_valid_jwt":
if e.complexity.Query.IsValidJwt == nil {
break
}
args, err := ec.field_Query_is_valid_jwt_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Query.IsValidJwt(childComplexity, args["params"].(*model.IsValidJWTQueryInput)), true
case "Query.meta":
if e.complexity.Query.Meta == nil {
break
@@ -982,20 +1003,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Users.Users(childComplexity), true
case "ValidJWTResponse.message":
if e.complexity.ValidJWTResponse.Message == nil {
break
}
return e.complexity.ValidJWTResponse.Message(childComplexity), true
case "ValidJWTResponse.valid":
if e.complexity.ValidJWTResponse.Valid == nil {
break
}
return e.complexity.ValidJWTResponse.Valid(childComplexity), true
case "VerificationRequest.created_at":
if e.complexity.VerificationRequest.CreatedAt == nil {
break
@@ -1139,6 +1146,7 @@ type Pagination {
type Meta {
version: String!
client_id: String!
is_google_login_enabled: Boolean!
is_facebook_login_enabled: Boolean!
is_github_login_enabled: Boolean!
@@ -1196,7 +1204,9 @@ type Error {
type AuthResponse {
message: String!
access_token: String
expires_at: Int64
id_token: String
refresh_token: String
expires_in: Int64
user: User
}
@@ -1204,16 +1214,13 @@ type Response {
message: String!
}
type ValidJWTResponse {
valid: Boolean!
message: String!
}
type Env {
ADMIN_SECRET: String
DATABASE_NAME: String
DATABASE_URL: String
DATABASE_TYPE: String
DATABASE_NAME: String!
DATABASE_URL: String!
DATABASE_TYPE: String!
CLIENT_ID: String!
CLIENT_SECRET: String!
CUSTOM_ACCESS_TOKEN_SCRIPT: String
SMTP_HOST: String
SMTP_PORT: String
@@ -1310,6 +1317,7 @@ input LoginInput {
email: String!
password: String!
roles: [String!]
scope: [String!]
}
input VerifyEmailInput {
@@ -1368,15 +1376,12 @@ input DeleteUserInput {
input MagicLinkLoginInput {
email: String!
roles: [String!]
scope: [String!]
}
input SessionQueryInput {
roles: [String!]
}
input IsValidJWTQueryInput {
jwt: String
roles: [String!]
scope: [String!]
}
input PaginationInput {
@@ -1410,7 +1415,6 @@ type Mutation {
type Query {
meta: Meta!
session(params: SessionQueryInput): AuthResponse!
is_valid_jwt(params: IsValidJWTQueryInput): ValidJWTResponse!
profile: User!
# admin only apis
_users(params: PaginatedInput): Users!
@@ -1666,21 +1670,6 @@ func (ec *executionContext) field_Query__verification_requests_args(ctx context.
return args, nil
}
func (ec *executionContext) field_Query_is_valid_jwt_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 *model.IsValidJWTQueryInput
if tmp, ok := rawArgs["params"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params"))
arg0, err = ec.unmarshalOIsValidJWTQueryInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐIsValidJWTQueryInput(ctx, tmp)
if err != nil {
return nil, err
}
}
args["params"] = arg0
return args, nil
}
func (ec *executionContext) field_Query_session_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@@ -1801,7 +1790,7 @@ func (ec *executionContext) _AuthResponse_access_token(ctx context.Context, fiel
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _AuthResponse_expires_at(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) {
func (ec *executionContext) _AuthResponse_id_token(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
@@ -1819,7 +1808,71 @@ func (ec *executionContext) _AuthResponse_expires_at(ctx context.Context, field
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
return obj.IDToken, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _AuthResponse_refresh_token(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "AuthResponse",
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.RefreshToken, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _AuthResponse_expires_in(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "AuthResponse",
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.ExpiresIn, nil
})
if err != nil {
ec.Error(ctx, err)
@@ -1922,11 +1975,14 @@ func (ec *executionContext) _Env_DATABASE_NAME(ctx context.Context, field graphq
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*string)
res := resTmp.(string)
fc.Result = res
return ec.marshalOString2string(ctx, field.Selections, res)
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_DATABASE_URL(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
@@ -1954,11 +2010,14 @@ func (ec *executionContext) _Env_DATABASE_URL(ctx context.Context, field graphql
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*string)
res := resTmp.(string)
fc.Result = res
return ec.marshalOString2string(ctx, field.Selections, res)
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_DATABASE_TYPE(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
@@ -1986,11 +2045,84 @@ func (ec *executionContext) _Env_DATABASE_TYPE(ctx context.Context, field graphq
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*string)
res := resTmp.(string)
fc.Result = res
return ec.marshalOString2string(ctx, field.Selections, res)
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_CLIENT_ID(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Env",
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.ClientID, 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.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_CLIENT_SECRET(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Env",
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.ClientSecret, 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.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_CUSTOM_ACCESS_TOKEN_SCRIPT(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
@@ -3090,6 +3222,41 @@ func (ec *executionContext) _Meta_version(ctx context.Context, field graphql.Col
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _Meta_client_id(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Meta",
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.ClientID, 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.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _Meta_is_google_login_enabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -4133,48 +4300,6 @@ func (ec *executionContext) _Query_session(ctx context.Context, field graphql.Co
return ec.marshalNAuthResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_is_valid_jwt(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Query",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Query_is_valid_jwt_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
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().IsValidJwt(rctx, args["params"].(*model.IsValidJWTQueryInput))
})
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.(*model.ValidJWTResponse)
fc.Result = res
return ec.marshalNValidJWTResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidJWTResponse(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_profile(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -5099,76 +5224,6 @@ func (ec *executionContext) _Users_users(ctx context.Context, field graphql.Coll
return ec.marshalNUser2ᚕᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐUserᚄ(ctx, field.Selections, res)
}
func (ec *executionContext) _ValidJWTResponse_valid(ctx context.Context, field graphql.CollectedField, obj *model.ValidJWTResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "ValidJWTResponse",
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.Valid, 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.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _ValidJWTResponse_message(ctx context.Context, field graphql.CollectedField, obj *model.ValidJWTResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "ValidJWTResponse",
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.Message, 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.(string)
fc.Result = res
return ec.marshalNString2string(ctx, field.Selections, res)
}
func (ec *executionContext) _VerificationRequest_id(ctx context.Context, field graphql.CollectedField, obj *model.VerificationRequest) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -6680,37 +6735,6 @@ func (ec *executionContext) unmarshalInputForgotPasswordInput(ctx context.Contex
return it, nil
}
func (ec *executionContext) unmarshalInputIsValidJWTQueryInput(ctx context.Context, obj interface{}) (model.IsValidJWTQueryInput, error) {
var it model.IsValidJWTQueryInput
asMap := map[string]interface{}{}
for k, v := range obj.(map[string]interface{}) {
asMap[k] = v
}
for k, v := range asMap {
switch k {
case "jwt":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("jwt"))
it.Jwt, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "roles":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roles"))
it.Roles, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj interface{}) (model.LoginInput, error) {
var it model.LoginInput
asMap := map[string]interface{}{}
@@ -6744,6 +6768,14 @@ func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj in
if err != nil {
return it, err
}
case "scope":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scope"))
it.Scope, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
if err != nil {
return it, err
}
}
}
@@ -6775,6 +6807,14 @@ func (ec *executionContext) unmarshalInputMagicLinkLoginInput(ctx context.Contex
if err != nil {
return it, err
}
case "scope":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scope"))
it.Scope, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
if err != nil {
return it, err
}
}
}
@@ -6922,6 +6962,14 @@ func (ec *executionContext) unmarshalInputSessionQueryInput(ctx context.Context,
if err != nil {
return it, err
}
case "scope":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("scope"))
it.Scope, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
if err != nil {
return it, err
}
}
}
@@ -7589,8 +7637,12 @@ func (ec *executionContext) _AuthResponse(ctx context.Context, sel ast.Selection
}
case "access_token":
out.Values[i] = ec._AuthResponse_access_token(ctx, field, obj)
case "expires_at":
out.Values[i] = ec._AuthResponse_expires_at(ctx, field, obj)
case "id_token":
out.Values[i] = ec._AuthResponse_id_token(ctx, field, obj)
case "refresh_token":
out.Values[i] = ec._AuthResponse_refresh_token(ctx, field, obj)
case "expires_in":
out.Values[i] = ec._AuthResponse_expires_in(ctx, field, obj)
case "user":
out.Values[i] = ec._AuthResponse_user(ctx, field, obj)
default:
@@ -7619,10 +7671,29 @@ func (ec *executionContext) _Env(ctx context.Context, sel ast.SelectionSet, obj
out.Values[i] = ec._Env_ADMIN_SECRET(ctx, field, obj)
case "DATABASE_NAME":
out.Values[i] = ec._Env_DATABASE_NAME(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "DATABASE_URL":
out.Values[i] = ec._Env_DATABASE_URL(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "DATABASE_TYPE":
out.Values[i] = ec._Env_DATABASE_TYPE(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "CLIENT_ID":
out.Values[i] = ec._Env_CLIENT_ID(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "CLIENT_SECRET":
out.Values[i] = ec._Env_CLIENT_SECRET(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "CUSTOM_ACCESS_TOKEN_SCRIPT":
out.Values[i] = ec._Env_CUSTOM_ACCESS_TOKEN_SCRIPT(ctx, field, obj)
case "SMTP_HOST":
@@ -7744,6 +7815,11 @@ func (ec *executionContext) _Meta(ctx context.Context, sel ast.SelectionSet, obj
if out.Values[i] == graphql.Null {
invalids++
}
case "client_id":
out.Values[i] = ec._Meta_client_id(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "is_google_login_enabled":
out.Values[i] = ec._Meta_is_google_login_enabled(ctx, field, obj)
if out.Values[i] == graphql.Null {
@@ -7971,20 +8047,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
}
return res
})
case "is_valid_jwt":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Query_is_valid_jwt(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "profile":
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
@@ -8200,38 +8262,6 @@ func (ec *executionContext) _Users(ctx context.Context, sel ast.SelectionSet, ob
return out
}
var validJWTResponseImplementors = []string{"ValidJWTResponse"}
func (ec *executionContext) _ValidJWTResponse(ctx context.Context, sel ast.SelectionSet, obj *model.ValidJWTResponse) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, validJWTResponseImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("ValidJWTResponse")
case "valid":
out.Values[i] = ec._ValidJWTResponse_valid(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
case "message":
out.Values[i] = ec._ValidJWTResponse_message(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var verificationRequestImplementors = []string{"VerificationRequest"}
func (ec *executionContext) _VerificationRequest(ctx context.Context, sel ast.SelectionSet, obj *model.VerificationRequest) graphql.Marshaler {
@@ -8847,20 +8877,6 @@ func (ec *executionContext) marshalNUsers2ᚖgithubᚗcomᚋauthorizerdevᚋauth
return ec._Users(ctx, sel, v)
}
func (ec *executionContext) marshalNValidJWTResponse2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidJWTResponse(ctx context.Context, sel ast.SelectionSet, v model.ValidJWTResponse) graphql.Marshaler {
return ec._ValidJWTResponse(ctx, sel, &v)
}
func (ec *executionContext) marshalNValidJWTResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidJWTResponse(ctx context.Context, sel ast.SelectionSet, v *model.ValidJWTResponse) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
return ec._ValidJWTResponse(ctx, sel, v)
}
func (ec *executionContext) marshalNVerificationRequest2ᚕᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐVerificationRequestᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.VerificationRequest) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup
@@ -9230,14 +9246,6 @@ func (ec *executionContext) marshalOInt642ᚖint64(ctx context.Context, sel ast.
return graphql.MarshalInt64(*v)
}
func (ec *executionContext) unmarshalOIsValidJWTQueryInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐIsValidJWTQueryInput(ctx context.Context, v interface{}) (*model.IsValidJWTQueryInput, error) {
if v == nil {
return nil, nil
}
res, err := ec.unmarshalInputIsValidJWTQueryInput(ctx, v)
return &res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalOPaginatedInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐPaginatedInput(ctx context.Context, v interface{}) (*model.PaginatedInput, error) {
if v == nil {
return nil, nil

View File

@@ -11,10 +11,12 @@ type AdminSignupInput struct {
}
type AuthResponse struct {
Message string `json:"message"`
AccessToken *string `json:"access_token"`
ExpiresAt *int64 `json:"expires_at"`
User *User `json:"user"`
Message string `json:"message"`
AccessToken *string `json:"access_token"`
IDToken *string `json:"id_token"`
RefreshToken *string `json:"refresh_token"`
ExpiresIn *int64 `json:"expires_in"`
User *User `json:"user"`
}
type DeleteUserInput struct {
@@ -23,9 +25,11 @@ type DeleteUserInput struct {
type Env struct {
AdminSecret *string `json:"ADMIN_SECRET"`
DatabaseName *string `json:"DATABASE_NAME"`
DatabaseURL *string `json:"DATABASE_URL"`
DatabaseType *string `json:"DATABASE_TYPE"`
DatabaseName string `json:"DATABASE_NAME"`
DatabaseURL string `json:"DATABASE_URL"`
DatabaseType string `json:"DATABASE_TYPE"`
ClientID string `json:"CLIENT_ID"`
ClientSecret string `json:"CLIENT_SECRET"`
CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"`
SMTPHost *string `json:"SMTP_HOST"`
SMTPPort *string `json:"SMTP_PORT"`
@@ -68,24 +72,22 @@ type ForgotPasswordInput struct {
Email string `json:"email"`
}
type IsValidJWTQueryInput struct {
Jwt *string `json:"jwt"`
Roles []string `json:"roles"`
}
type LoginInput struct {
Email string `json:"email"`
Password string `json:"password"`
Roles []string `json:"roles"`
Scope []string `json:"scope"`
}
type MagicLinkLoginInput struct {
Email string `json:"email"`
Roles []string `json:"roles"`
Scope []string `json:"scope"`
}
type Meta struct {
Version string `json:"version"`
ClientID string `json:"client_id"`
IsGoogleLoginEnabled bool `json:"is_google_login_enabled"`
IsFacebookLoginEnabled bool `json:"is_facebook_login_enabled"`
IsGithubLoginEnabled bool `json:"is_github_login_enabled"`
@@ -127,6 +129,7 @@ type Response struct {
type SessionQueryInput struct {
Roles []string `json:"roles"`
Scope []string `json:"scope"`
}
type SignUpInput struct {
@@ -235,11 +238,6 @@ type Users struct {
Users []*User `json:"users"`
}
type ValidJWTResponse struct {
Valid bool `json:"valid"`
Message string `json:"message"`
}
type VerificationRequest struct {
ID string `json:"id"`
Identifier *string `json:"identifier"`

View File

@@ -14,6 +14,7 @@ type Pagination {
type Meta {
version: String!
client_id: String!
is_google_login_enabled: Boolean!
is_facebook_login_enabled: Boolean!
is_github_login_enabled: Boolean!
@@ -71,7 +72,9 @@ type Error {
type AuthResponse {
message: String!
access_token: String
expires_at: Int64
id_token: String
refresh_token: String
expires_in: Int64
user: User
}
@@ -79,16 +82,13 @@ type Response {
message: String!
}
type ValidJWTResponse {
valid: Boolean!
message: String!
}
type Env {
ADMIN_SECRET: String
DATABASE_NAME: String
DATABASE_URL: String
DATABASE_TYPE: String
DATABASE_NAME: String!
DATABASE_URL: String!
DATABASE_TYPE: String!
CLIENT_ID: String!
CLIENT_SECRET: String!
CUSTOM_ACCESS_TOKEN_SCRIPT: String
SMTP_HOST: String
SMTP_PORT: String
@@ -185,6 +185,7 @@ input LoginInput {
email: String!
password: String!
roles: [String!]
scope: [String!]
}
input VerifyEmailInput {
@@ -243,15 +244,12 @@ input DeleteUserInput {
input MagicLinkLoginInput {
email: String!
roles: [String!]
scope: [String!]
}
input SessionQueryInput {
roles: [String!]
}
input IsValidJWTQueryInput {
jwt: String
roles: [String!]
scope: [String!]
}
input PaginationInput {
@@ -285,7 +283,6 @@ type Mutation {
type Query {
meta: Meta!
session(params: SessionQueryInput): AuthResponse!
is_valid_jwt(params: IsValidJWTQueryInput): ValidJWTResponse!
profile: User!
# admin only apis
_users(params: PaginatedInput): Users!

View File

@@ -79,10 +79,6 @@ func (r *queryResolver) Session(ctx context.Context, params *model.SessionQueryI
return resolvers.SessionResolver(ctx, params)
}
func (r *queryResolver) IsValidJwt(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error) {
return resolvers.IsValidJwtResolver(ctx, params)
}
func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) {
return resolvers.ProfileResolver(ctx)
}

View File

@@ -7,6 +7,7 @@ import (
"strings"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin"
@@ -23,7 +24,7 @@ type State struct {
func AppHandler() gin.HandlerFunc {
return func(c *gin.Context) {
hostname := utils.GetHost(c)
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableLoginPage) {
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableLoginPage) {
c.JSON(400, gin.H{"error": "login page is not enabled"})
return
}
@@ -36,7 +37,7 @@ func AppHandler() gin.HandlerFunc {
stateObj.AuthorizerURL = hostname
stateObj.RedirectURL = hostname + "/app"
} else {
decodedState, err := utils.DecryptB64(state)
decodedState, err := crypto.DecryptB64(state)
if err != nil {
c.JSON(400, gin.H{"error": "[unable to decode state] invalid state"})
return
@@ -79,8 +80,8 @@ func AppHandler() gin.HandlerFunc {
"data": map[string]string{
"authorizerURL": stateObj.AuthorizerURL,
"redirectURL": stateObj.RedirectURL,
"organizationName": envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName),
"organizationLogo": envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo),
"organizationName": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName),
"organizationLogo": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo),
},
})
}

View File

@@ -0,0 +1,271 @@
package handlers
import (
"net/http"
"strings"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/token"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// AuthorizeHandler is the handler for the /authorize route
// required params
// ?redirect_uri = redirect url
// state[recommended] = to prevent CSRF attack (for authorizer its compulsory)
// code_challenge = to prevent CSRF attack
// code_challenge_method = to prevent CSRF attack [only sh256 is supported]
// check the flow for generating and verifying codes: https://developer.okta.com/blog/2019/08/22/okta-authjs-pkce#:~:text=PKCE%20works%20by%20having%20the,is%20called%20the%20Code%20Challenge.
func AuthorizeHandler() gin.HandlerFunc {
return func(gc *gin.Context) {
redirectURI := strings.TrimSpace(gc.Query("redirect_uri"))
responseType := strings.TrimSpace(gc.Query("response_type"))
state := strings.TrimSpace(gc.Query("state"))
codeChallenge := strings.TrimSpace(gc.Query("code_challenge"))
scopeString := strings.TrimSpace(gc.Query("scope"))
clientID := strings.TrimSpace(gc.Query("client_id"))
template := "authorize.tmpl"
if clientID == "" {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "client_id is required",
},
},
})
return
}
if clientID != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "invalid_client_id",
},
},
})
return
}
if redirectURI == "" {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "redirect_uri is required",
},
},
})
return
}
if state == "" {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "state is required",
},
},
})
return
}
if responseType == "" {
responseType = "token"
}
var scope []string
if scopeString == "" {
scope = []string{"openid", "profile", "email"}
} else {
scope = strings.Split(scopeString, " ")
}
isResponseTypeCode := responseType == "code"
isResponseTypeToken := responseType == "token"
if !isResponseTypeCode && !isResponseTypeToken {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "response_type is invalid",
},
},
})
return
}
if isResponseTypeCode {
if codeChallenge == "" {
gc.HTML(http.StatusBadRequest, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "code_challenge is required",
},
},
})
return
}
}
sessionToken, err := cookie.GetSession(gc)
if err != nil {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
},
})
return
}
// get session from cookie
claims, err := token.ValidateBrowserSession(gc, sessionToken)
if err != nil {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
},
})
return
}
userID := claims.Subject
user, err := db.Provider.GetUserByID(userID)
if err != nil {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "signup_required",
"error_description": "Sign up required",
},
},
})
return
}
// if user is logged in
// based on the response type, generate the response
if isResponseTypeCode {
// rollover the session for security
sessionstore.RemoveState(sessionToken)
nonce := uuid.New().String()
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope)
if err != nil {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
},
})
return
}
sessionstore.SetState(newSessionToken, newSessionTokenData.Nonce+"@"+user.ID)
cookie.SetSession(gc, newSessionToken)
code := uuid.New().String()
sessionstore.SetState(codeChallenge, code+"@"+newSessionToken)
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"code": code,
"state": state,
},
},
})
return
}
if isResponseTypeToken {
// rollover the session for security
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope)
if err != nil {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
},
})
return
}
sessionstore.RemoveState(sessionToken)
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := int64(1800)
res := map[string]interface{}{
"access_token": authToken.AccessToken.Token,
"id_token": authToken.IDToken.Token,
"state": state,
"scope": scope,
"token_type": "Bearer",
"expires_in": expiresIn,
}
if authToken.RefreshToken != nil {
res["refresh_token"] = authToken.RefreshToken.Token
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
}
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": res,
},
})
return
}
// by default return with error
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
},
})
}
}

View File

@@ -13,7 +13,7 @@ func DashboardHandler() gin.HandlerFunc {
return func(c *gin.Context) {
isOnboardingCompleted := false
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) != "" {
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) != "" {
isOnboardingCompleted = true
}

30
server/handlers/jwks.go Normal file
View File

@@ -0,0 +1,30 @@
package handlers
import (
"encoding/json"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/gin-gonic/gin"
)
func JWKsHandler() gin.HandlerFunc {
return func(c *gin.Context) {
var data map[string]string
jwk := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJWK)
err := json.Unmarshal([]byte(jwk), &data)
if err != nil {
c.JSON(500, gin.H{
"error": err.Error(),
})
return
}
c.JSON(200, gin.H{
"keys": []map[string]string{
data,
},
})
}
}

40
server/handlers/logout.go Normal file
View File

@@ -0,0 +1,40 @@
package handlers
import (
"net/http"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/gin-gonic/gin"
)
func LogoutHandler() gin.HandlerFunc {
return func(gc *gin.Context) {
// get fingerprint hash
fingerprintHash, err := cookie.GetSession(gc)
if err != nil {
gc.JSON(http.StatusUnauthorized, gin.H{
"error": err.Error(),
})
return
}
decryptedFingerPrint, err := crypto.DecryptAES(fingerprintHash)
if err != nil {
gc.JSON(http.StatusUnauthorized, gin.H{
"error": err.Error(),
})
return
}
fingerPrint := string(decryptedFingerPrint)
sessionstore.RemoveState(fingerPrint)
cookie.DeleteSession(gc)
gc.JSON(http.StatusOK, gin.H{
"message": "Logged out successfully",
})
}
}

View File

@@ -21,6 +21,7 @@ import (
"github.com/authorizerdev/authorizer/server/utils"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"golang.org/x/oauth2"
)
@@ -30,11 +31,11 @@ func OAuthCallbackHandler() gin.HandlerFunc {
provider := c.Param("oauth_provider")
state := c.Request.FormValue("state")
sessionState := sessionstore.GetSocailLoginState(state)
sessionState := sessionstore.GetState(state)
if sessionState == "" {
c.JSON(400, gin.H{"error": "invalid oauth state"})
}
sessionstore.RemoveSocialLoginState(state)
sessionstore.GetState(state)
// contains random token, redirect url, role
sessionSplit := strings.Split(state, "___")
@@ -74,7 +75,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
// make sure inputRoles don't include protected roles
hasProtectedRole := false
for _, ir := range inputRoles {
if utils.StringSliceContains(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ir) {
if utils.StringSliceContains(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ir) {
hasProtectedRole = true
}
}
@@ -122,7 +123,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
// check if it contains protected unassigned role
hasProtectedRole := false
for _, ur := range unasignedRoles {
if utils.StringSliceContains(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ur) {
if utils.StringSliceContains(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ur) {
hasProtectedRole = true
}
}
@@ -144,11 +145,17 @@ func OAuthCallbackHandler() gin.HandlerFunc {
}
}
authToken, _ := token.CreateAuthToken(user, inputRoles)
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
cookie.SetCookie(c, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
utils.SaveSessionInDB(user.ID, c)
// TODO use query param
scope := []string{"openid", "email", "profile"}
nonce := uuid.New().String()
_, newSessionToken, err := token.CreateSessionToken(user, nonce, inputRoles, scope)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
}
sessionstore.SetState(newSessionToken, nonce+"@"+user.ID)
cookie.SetSession(c, newSessionToken)
go utils.SaveSessionInDB(c, user.ID)
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
}
}
@@ -227,7 +234,7 @@ func processGithubUserInfo(code string) (models.User, error) {
GivenName: &firstName,
FamilyName: &lastName,
Picture: &picture,
Email: userRawData["email"],
Email: userRawData["sub"],
}
return user, nil
@@ -260,7 +267,7 @@ func processFacebookUserInfo(code string) (models.User, error) {
userRawData := make(map[string]interface{})
json.Unmarshal(body, &userRawData)
email := fmt.Sprintf("%v", userRawData["email"])
email := fmt.Sprintf("%v", userRawData["sub"])
picObject := userRawData["picture"].(map[string]interface{})["data"]
picDataObject := picObject.(map[string]interface{})

View File

@@ -33,14 +33,14 @@ func OAuthLoginHandler() gin.HandlerFunc {
// 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(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...), rolesSplit) {
if !utils.IsValidRoles(append([]string{}, append(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...), rolesSplit) {
c.JSON(400, gin.H{
"error": "invalid role",
})
return
}
} else {
roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
}
uuid := uuid.New()
@@ -54,7 +54,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
isProviderConfigured = false
break
}
sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodGoogle)
sessionstore.SetState(oauthStateString, constants.SignupMethodGoogle)
// during the init of OAuthProvider authorizer url might be empty
oauth.OAuthProviders.GoogleConfig.RedirectURL = hostname + "/oauth_callback/google"
url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
@@ -64,7 +64,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
isProviderConfigured = false
break
}
sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodGithub)
sessionstore.SetState(oauthStateString, constants.SignupMethodGithub)
oauth.OAuthProviders.GithubConfig.RedirectURL = hostname + "/oauth_callback/github"
url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
@@ -73,7 +73,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
isProviderConfigured = false
break
}
sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodFacebook)
sessionstore.SetState(oauthStateString, constants.SignupMethodFacebook)
oauth.OAuthProviders.FacebookConfig.RedirectURL = hostname + "/oauth_callback/facebook"
url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)

View File

@@ -0,0 +1,30 @@
package handlers
import (
"github.com/gin-gonic/gin"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils"
)
// OpenIDConfigurationHandler handler for open-id configurations
func OpenIDConfigurationHandler() gin.HandlerFunc {
return func(c *gin.Context) {
issuer := utils.GetHost(c)
jwtType := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
c.JSON(200, gin.H{
"issuer": issuer,
"authorization_endpoint": issuer + "/authorize",
"token_endpoint": issuer + "/token",
"userinfo_endpoint": issuer + "/userinfo",
"jwks_uri": issuer + "/.well-known/jwks.json",
"response_types_supported": []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token"},
"scopes_supported": []string{"openid", "email", "profile", "email_verified", "given_name", "family_name", "nick_name", "picture"},
"response_modes_supported": []string{"query", "fragment", "form_post"},
"id_token_signing_alg_values_supported": []string{jwtType},
"claims_supported": []string{"aud", "exp", "iss", "iat", "sub", "given_name", "family_name", "middle_name", "nickname", "preferred_username", "picture", "email", "email_verified", "roles", "gender", "birthdate", "phone_number", "phone_number_verified"},
})
}
}

138
server/handlers/token.go Normal file
View File

@@ -0,0 +1,138 @@
package handlers
import (
"crypto/sha256"
"encoding/base64"
"net/http"
"strings"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/token"
"github.com/gin-gonic/gin"
)
func TokenHandler() gin.HandlerFunc {
return func(gc *gin.Context) {
var reqBody map[string]string
if err := gc.BindJSON(&reqBody); err != nil {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "error_binding_json",
"error_description": err.Error(),
})
return
}
codeVerifier := strings.TrimSpace(reqBody["code_verifier"])
code := strings.TrimSpace(reqBody["code"])
clientID := strings.TrimSpace(reqBody["client_id"])
if clientID == "" {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "client_id_required",
"error_description": "The client id is required",
})
return
}
if clientID != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_client_id",
"error_description": "The client id is invalid",
})
return
}
if codeVerifier == "" {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_code_verifier",
"error_description": "The code verifier is required",
})
return
}
if code == "" {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_code",
"error_description": "The code is required",
})
return
}
hash := sha256.New()
hash.Write([]byte(codeVerifier))
encryptedCode := strings.ReplaceAll(base64.URLEncoding.EncodeToString(hash.Sum(nil)), "+", "-")
encryptedCode = strings.ReplaceAll(encryptedCode, "/", "_")
encryptedCode = strings.ReplaceAll(encryptedCode, "=", "")
sessionData := sessionstore.GetState(encryptedCode)
if sessionData == "" {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_code_verifier",
"error_description": "The code verifier is invalid",
})
return
}
// split session data
// it contains code@sessiontoken
sessionDataSplit := strings.Split(sessionData, "@")
if sessionDataSplit[0] != code {
gc.JSON(http.StatusBadRequest, gin.H{
"error": "invalid_code_verifier",
"error_description": "The code verifier is invalid",
})
return
}
// validate session
claims, err := token.ValidateBrowserSession(gc, sessionDataSplit[1])
if err != nil {
gc.JSON(http.StatusUnauthorized, gin.H{
"error": "unauthorized",
"error_description": "Invalid session data",
})
return
}
userID := claims.Subject
user, err := db.Provider.GetUserByID(userID)
if err != nil {
gc.JSON(http.StatusUnauthorized, gin.H{
"error": "unauthorized",
"error_description": "User not found",
})
return
}
// rollover the session for security
sessionstore.RemoveState(sessionDataSplit[1])
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, claims.Scope)
if err != nil {
gc.JSON(http.StatusUnauthorized, gin.H{
"error": "unauthorized",
"error_description": "User not found",
})
return
}
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := int64(1800)
res := map[string]interface{}{
"access_token": authToken.AccessToken.Token,
"id_token": authToken.IDToken.Token,
"scope": claims.Scope,
"expires_in": expiresIn,
}
if authToken.RefreshToken != nil {
res["refresh_token"] = authToken.RefreshToken.Token
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
}
gc.JSON(http.StatusOK, res)
}
}

View File

@@ -0,0 +1,40 @@
package handlers
import (
"net/http"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/token"
"github.com/gin-gonic/gin"
)
func UserInfoHandler() gin.HandlerFunc {
return func(gc *gin.Context) {
accessToken, err := token.GetAccessToken(gc)
if err != nil {
gc.JSON(http.StatusUnauthorized, gin.H{
"error": err.Error(),
})
return
}
claims, err := token.ValidateAccessToken(gc, accessToken)
if err != nil {
gc.JSON(http.StatusUnauthorized, gin.H{
"error": err.Error(),
})
return
}
userID := claims["sub"].(string)
user, err := db.Provider.GetUserByID(userID)
if err != nil {
gc.JSON(http.StatusUnauthorized, gin.H{
"error": err.Error(),
})
return
}
gc.JSON(http.StatusOK, user.AsAPIUser())
}
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// VerifyEmailHandler handles the verify email route.
@@ -18,7 +19,7 @@ import (
func VerifyEmailHandler() gin.HandlerFunc {
return func(c *gin.Context) {
errorRes := gin.H{
"message": "invalid token",
"error": "invalid token",
}
tokenInQuery := c.Query("token")
if tokenInQuery == "" {
@@ -33,13 +34,21 @@ func VerifyEmailHandler() gin.HandlerFunc {
}
// verify if token exists in db
claim, err := token.ParseJWTToken(tokenInQuery)
hostname := utils.GetHost(c)
encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce)
if err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
return
}
claim, err := token.ParseJWTToken(tokenInQuery, hostname, encryptedNonce, verificationRequest.Email)
if err != nil {
c.JSON(400, errorRes)
return
}
user, err := db.Provider.GetUserByEmail(claim["email"].(string))
user, err := db.Provider.GetUserByEmail(claim["sub"].(string))
if err != nil {
c.JSON(400, gin.H{
"message": err.Error(),
@@ -57,16 +66,19 @@ func VerifyEmailHandler() gin.HandlerFunc {
db.Provider.DeleteVerificationRequest(verificationRequest)
roles := strings.Split(user.Roles, ",")
authToken, err := token.CreateAuthToken(user, roles)
scope := []string{"openid", "email", "profile"}
nonce := uuid.New().String()
_, authToken, err := token.CreateSessionToken(user, nonce, roles, scope)
if err != nil {
c.JSON(400, gin.H{
"message": err.Error(),
})
return
}
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
cookie.SetCookie(c, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
utils.SaveSessionInDB(user.ID, c)
sessionstore.SetState(authToken, nonce+"@"+user.ID)
cookie.SetSession(c, authToken)
go utils.SaveSessionInDB(c, user.ID)
c.Redirect(http.StatusTemporaryRedirect, claim["redirect_url"].(string))
}

View File

@@ -2,6 +2,7 @@ package main
import (
"flag"
"log"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
@@ -20,16 +21,45 @@ func main() {
envstore.ARG_ENV_FILE = flag.String("env_file", "", "Env file path")
flag.Parse()
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, VERSION)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, VERSION)
env.InitEnv()
db.InitDB()
env.PersistEnv()
// initialize required envs (mainly db & env file path)
err := env.InitRequiredEnv()
if err != nil {
log.Fatal("Error while initializing required envs:", err)
}
sessionstore.InitSession()
oauth.InitOAuth()
// initialize db provider
err = db.InitDB()
if err != nil {
log.Fatalln("Error while initializing db:", err)
}
// initialize all envs
// (get if present from db else construct from os env + defaults)
err = env.InitAllEnv()
if err != nil {
log.Fatalln("Error while initializing env: ", err)
}
// persist all envs
err = env.PersistEnv()
if err != nil {
log.Fatalln("Error while persisting env:", err)
}
// initialize session store (redis or in-memory based on env)
err = sessionstore.InitSession()
if err != nil {
log.Fatalln("Error while initializing session store:", err)
}
// initialize oauth providers based on env
err = oauth.InitOAuth()
if err != nil {
log.Fatalln("Error while initializing oauth:", err)
}
router := routes.InitRouter()
router.Run(":" + envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyPort))
router.Run(":" + envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyPort))
}

View File

@@ -2,7 +2,6 @@ package oauth
import (
"context"
"log"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
@@ -32,37 +31,39 @@ var (
)
// InitOAuth initializes the OAuth providers based on EnvData
func InitOAuth() {
func InitOAuth() error {
ctx := context.Background()
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret) != "" {
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret) != "" {
p, err := oidc.NewProvider(ctx, "https://accounts.google.com")
if err != nil {
log.Fatalln("error creating oidc provider for google:", err)
return err
}
OIDCProviders.GoogleOIDC = p
OAuthProviders.GoogleConfig = &oauth2.Config{
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID),
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret),
ClientID: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID),
ClientSecret: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret),
RedirectURL: "/oauth_callback/google",
Endpoint: OIDCProviders.GoogleOIDC.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
}
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID) != "" && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret) != "" {
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret) != "" {
OAuthProviders.GithubConfig = &oauth2.Config{
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID),
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret),
ClientID: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID),
ClientSecret: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret),
RedirectURL: "/oauth_callback/github",
Endpoint: githubOAuth2.Endpoint,
}
}
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID) != "" && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" {
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID) != "" && envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID) != "" {
OAuthProviders.FacebookConfig = &oauth2.Config{
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID),
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientSecret),
ClientID: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID),
ClientSecret: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientSecret),
RedirectURL: "/oauth_callback/facebook",
Endpoint: facebookOAuth2.Endpoint,
Scopes: []string{"public_profile", "email"},
}
}
return nil
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/utils"
@@ -20,12 +21,12 @@ func AdminLoginResolver(ctx context.Context, params model.AdminLoginInput) (*mod
return res, err
}
adminSecret := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
adminSecret := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
if params.AdminSecret != adminSecret {
return res, fmt.Errorf(`invalid admin secret`)
}
hashedKey, err := utils.EncryptPassword(adminSecret)
hashedKey, err := crypto.EncryptPassword(adminSecret)
if err != nil {
return res, err
}

View File

@@ -6,6 +6,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/token"
@@ -25,7 +26,7 @@ func AdminSessionResolver(ctx context.Context) (*model.Response, error) {
return res, fmt.Errorf("unauthorized")
}
hashedKey, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
hashedKey, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
if err != nil {
return res, err
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
@@ -33,18 +34,18 @@ func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*m
return res, err
}
adminSecret := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
adminSecret := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
if adminSecret != "" {
err = fmt.Errorf("admin sign up already completed")
return res, err
}
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, params.AdminSecret)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, params.AdminSecret)
// consvert EnvData to JSON
var storeData envstore.Store
jsonBytes, err := json.Marshal(envstore.EnvInMemoryStoreObj.GetEnvStoreClone())
jsonBytes, err := json.Marshal(envstore.EnvStoreObj.GetEnvStoreClone())
if err != nil {
return res, err
}
@@ -58,7 +59,7 @@ func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*m
return res, err
}
envData, err := utils.EncryptEnvData(storeData)
envData, err := crypto.EncryptEnvData(storeData)
if err != nil {
return res, err
}
@@ -68,7 +69,7 @@ func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*m
return res, err
}
hashedKey, err := utils.EncryptPassword(params.AdminSecret)
hashedKey, err := crypto.EncryptPassword(params.AdminSecret)
if err != nil {
return res, err
}

View File

@@ -29,7 +29,7 @@ func DeleteUserResolver(ctx context.Context, params model.DeleteUserInput) (*mod
return res, err
}
sessionstore.DeleteAllUserSession(fmt.Sprintf("%x", user.ID))
go sessionstore.DeleteAllUserSession(fmt.Sprintf("%x", user.ID))
err = db.Provider.DeleteUser(user)
if err != nil {

View File

@@ -26,8 +26,10 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
}
// get clone of store
store := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
store := envstore.EnvStoreObj.GetEnvStoreClone()
adminSecret := store.StringEnv[constants.EnvKeyAdminSecret]
clientID := store.StringEnv[constants.EnvKeyClientID]
clientSecret := store.StringEnv[constants.EnvKeyClientSecret]
databaseURL := store.StringEnv[constants.EnvKeyDatabaseURL]
databaseName := store.StringEnv[constants.EnvKeyDatabaseName]
databaseType := store.StringEnv[constants.EnvKeyDatabaseType]
@@ -65,9 +67,11 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
res = &model.Env{
AdminSecret: &adminSecret,
DatabaseName: &databaseName,
DatabaseURL: &databaseURL,
DatabaseType: &databaseType,
DatabaseName: databaseName,
DatabaseURL: databaseURL,
DatabaseType: databaseType,
ClientID: clientID,
ClientSecret: clientSecret,
CustomAccessTokenScript: &customAccessTokenScript,
SMTPHost: &smtpHost,
SMTPPort: &smtpPort,

View File

@@ -24,7 +24,7 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
if err != nil {
return res, err
}
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
}
params.Email = strings.ToLower(params.Email)
@@ -39,7 +39,11 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
}
hostname := utils.GetHost(gc)
verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname)
nonce, nonceHash, err := utils.GenerateNonce()
if err != nil {
return res, err
}
verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname, nonceHash)
if err != nil {
log.Println(`error generating token`, err)
}
@@ -48,12 +52,11 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
Identifier: constants.VerificationTypeForgotPassword,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: params.Email,
Nonce: nonce,
})
// exec it as go routin so that we can reduce the api latency
go func() {
email.SendForgotPasswordMail(params.Email, verificationToken, hostname)
}()
go email.SendForgotPasswordMail(params.Email, verificationToken, hostname)
res = &model.Response{
Message: `Please check your inbox! We have sent a password reset link.`,

View File

@@ -1,52 +0,0 @@
package resolvers
import (
"context"
"errors"
"fmt"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/token"
tokenHelper "github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
)
// IsValidJwtResolver resolver to return if given jwt is valid
func IsValidJwtResolver(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error) {
gc, err := utils.GinContextFromContext(ctx)
token, err := token.GetAccessToken(gc)
if token == "" || err != nil {
if params != nil && *params.Jwt != "" {
token = *params.Jwt
} else {
return nil, errors.New("no jwt provided via cookie / header / params")
}
}
claims, err := tokenHelper.ParseJWTToken(token)
if err != nil {
return nil, err
}
claimRoleInterface := claims[envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim)].([]interface{})
claimRoles := []string{}
for _, v := range claimRoleInterface {
claimRoles = append(claimRoles, v.(string))
}
if params != nil && params.Roles != nil && len(params.Roles) > 0 {
for _, v := range params.Roles {
if !utils.StringSliceContains(claimRoles, v) {
return nil, fmt.Errorf(`unauthorized`)
}
}
}
return &model.ValidJWTResponse{
Valid: true,
Message: "Valid JWT",
}, nil
}

View File

@@ -25,7 +25,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
return res, err
}
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
}
@@ -49,7 +49,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
log.Println("compare password error:", err)
return res, fmt.Errorf(`invalid password`)
}
roles := envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)
roles := envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)
currentRoles := strings.Split(user.Roles, ",")
if len(params.Roles) > 0 {
if !utils.IsValidRoles(currentRoles, params.Roles) {
@@ -59,20 +59,36 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
roles = params.Roles
}
authToken, err := token.CreateAuthToken(user, roles)
scope := []string{"openid", "email", "profile"}
if params.Scope != nil && len(scope) > 0 {
scope = params.Scope
}
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
if err != nil {
return res, err
}
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
utils.SaveSessionInDB(user.ID, gc)
cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := int64(1800)
res = &model.AuthResponse{
Message: `Logged in successfully`,
AccessToken: &authToken.AccessToken.Token,
ExpiresAt: &authToken.AccessToken.ExpiresAt,
IDToken: &authToken.IDToken.Token,
ExpiresIn: &expiresIn,
User: user.AsAPIUser(),
}
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
}
go utils.SaveSessionInDB(gc, user.ID)
return res, nil
}

View File

@@ -4,9 +4,9 @@ import (
"context"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
)
@@ -18,34 +18,21 @@ func LogoutResolver(ctx context.Context) (*model.Response, error) {
return res, err
}
// get refresh token
refreshToken, err := token.GetRefreshToken(gc)
if err != nil {
return res, err
}
// get fingerprint hash
fingerprintHash, err := token.GetFingerPrint(gc)
fingerprintHash, err := cookie.GetSession(gc)
if err != nil {
return res, err
}
decryptedFingerPrint, err := utils.DecryptAES([]byte(fingerprintHash))
decryptedFingerPrint, err := crypto.DecryptAES(fingerprintHash)
if err != nil {
return res, err
}
fingerPrint := string(decryptedFingerPrint)
// verify refresh token and fingerprint
claims, err := token.ParseJWTToken(refreshToken)
if err != nil {
return res, err
}
userID := claims["id"].(string)
sessionstore.DeleteUserSession(userID, fingerPrint)
cookie.DeleteCookie(gc)
sessionstore.RemoveState(fingerPrint)
cookie.DeleteSession(gc)
res = &model.Response{
Message: "Logged out successfully",

View File

@@ -25,7 +25,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
return res, err
}
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableMagicLinkLogin) {
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableMagicLinkLogin) {
return res, fmt.Errorf(`magic link login is disabled for this instance`)
}
@@ -49,13 +49,13 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
// define roles for new user
if len(params.Roles) > 0 {
// check if roles exists
if !utils.IsValidRoles(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), params.Roles) {
if !utils.IsValidRoles(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), params.Roles) {
return res, fmt.Errorf(`invalid roles`)
} else {
inputRoles = params.Roles
}
} else {
inputRoles = envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)
inputRoles = envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)
}
user.Roles = strings.Join(inputRoles, ",")
@@ -80,7 +80,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
// check if it contains protected unassigned role
hasProtectedRole := false
for _, ur := range unasignedRoles {
if utils.StringSliceContains(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ur) {
if utils.StringSliceContains(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ur) {
hasProtectedRole = true
}
}
@@ -107,10 +107,14 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
}
hostname := utils.GetHost(gc)
if !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
// insert verification request
nonce, nonceHash, err := utils.GenerateNonce()
if err != nil {
return res, err
}
verificationType := constants.VerificationTypeMagicLinkLogin
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname)
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname, nonceHash)
if err != nil {
log.Println(`error generating token`, err)
}
@@ -119,12 +123,11 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: params.Email,
Nonce: nonce,
})
// exec it as go routin so that we can reduce the api latency
go func() {
email.SendVerificationMail(params.Email, verificationToken, hostname)
}()
go email.SendVerificationMail(params.Email, verificationToken, hostname)
}
res = &model.Response{

View File

@@ -2,7 +2,6 @@ package resolvers
import (
"context"
"fmt"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
@@ -18,13 +17,17 @@ func ProfileResolver(ctx context.Context) (*model.User, error) {
return res, err
}
claims, err := token.ValidateAccessToken(gc)
accessToken, err := token.GetAccessToken(gc)
if err != nil {
return res, err
}
userID := fmt.Sprintf("%v", claims["id"])
claims, err := token.ValidateAccessToken(gc, accessToken)
if err != nil {
return res, err
}
userID := claims["sub"].(string)
user, err := db.Provider.GetUserByID(userID)
if err != nil {
return res, err

View File

@@ -44,7 +44,11 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
}
hostname := utils.GetHost(gc)
verificationToken, err := token.CreateVerificationToken(params.Email, params.Identifier, hostname)
nonce, nonceHash, err := utils.GenerateNonce()
if err != nil {
return res, err
}
verificationToken, err := token.CreateVerificationToken(params.Email, params.Identifier, hostname, nonceHash)
if err != nil {
log.Println(`error generating token`, err)
}
@@ -53,12 +57,11 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
Identifier: params.Identifier,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: params.Email,
Nonce: nonce,
})
// exec it as go routin so that we can reduce the api latency
go func() {
email.SendVerificationMail(params.Email, verificationToken, hostname)
}()
go email.SendVerificationMail(params.Email, verificationToken, hostname)
res = &model.Response{
Message: `Verification email has been sent. Please check your inbox`,

View File

@@ -7,6 +7,7 @@ import (
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
@@ -17,7 +18,11 @@ import (
// ResetPasswordResolver is a resolver for reset password mutation
func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) {
var res *model.Response
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
gc, err := utils.GinContextFromContext(ctx)
if err != nil {
return res, err
}
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
}
@@ -31,17 +36,22 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput)
}
// verify if token exists in db
claim, err := token.ParseJWTToken(params.Token)
hostname := utils.GetHost(gc)
encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce)
if err != nil {
return res, err
}
claim, err := token.ParseJWTToken(params.Token, hostname, encryptedNonce, verificationRequest.Email)
if err != nil {
return res, fmt.Errorf(`invalid token`)
}
user, err := db.Provider.GetUserByEmail(claim["email"].(string))
user, err := db.Provider.GetUserByEmail(claim["sub"].(string))
if err != nil {
return res, err
}
password, _ := utils.EncryptPassword(params.Password)
password, _ := crypto.EncryptPassword(params.Password)
user.Password = &password
signupMethod := user.SignupMethods

View File

@@ -13,6 +13,7 @@ import (
)
// SessionResolver is a resolver for session query
// TODO allow validating with code and code verifier instead of cookie (PKCE flow)
func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error) {
var res *model.AuthResponse
@@ -21,48 +22,27 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
return res, err
}
// get refresh token
refreshToken, err := token.GetRefreshToken(gc)
sessionToken, err := cookie.GetSession(gc)
if err != nil {
return res, err
}
// get fingerprint hash
fingerprintHash, err := token.GetFingerPrint(gc)
// get session from cookie
claims, err := token.ValidateBrowserSession(gc, sessionToken)
if err != nil {
return res, err
}
decryptedFingerPrint, err := utils.DecryptAES([]byte(fingerprintHash))
if err != nil {
return res, err
}
fingerPrint := string(decryptedFingerPrint)
// verify refresh token and fingerprint
claims, err := token.ParseJWTToken(refreshToken)
if err != nil {
return res, err
}
userID := claims["id"].(string)
persistedRefresh := sessionstore.GetUserSession(userID, fingerPrint)
if refreshToken != persistedRefresh {
return res, fmt.Errorf(`unauthorized`)
}
userID := claims.Subject
user, err := db.Provider.GetUserByID(userID)
if err != nil {
return res, err
}
// refresh token has "roles" as claim
claimRoleInterface := claims["roles"].([]interface{})
claimRoleInterface := claims.Roles
claimRoles := []string{}
for _, v := range claimRoleInterface {
claimRoles = append(claimRoles, v.(string))
claimRoles = append(claimRoles, v)
}
if params != nil && params.Roles != nil && len(params.Roles) > 0 {
@@ -73,22 +53,35 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
}
}
// delete older session
sessionstore.DeleteUserSession(userID, fingerPrint)
scope := []string{"openid", "email", "profile"}
if params != nil && params.Scope != nil && len(scope) > 0 {
scope = params.Scope
}
authToken, err := token.CreateAuthToken(user, claimRoles)
authToken, err := token.CreateAuthToken(gc, user, claimRoles, scope)
if err != nil {
return res, err
}
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
// rollover the session for security
sessionstore.RemoveState(sessionToken)
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := int64(1800)
res = &model.AuthResponse{
Message: `Session token refreshed`,
AccessToken: &authToken.AccessToken.Token,
ExpiresAt: &authToken.AccessToken.ExpiresAt,
ExpiresIn: &expiresIn,
IDToken: &authToken.IDToken.Token,
User: user.AsAPIUser(),
}
if authToken.RefreshToken != nil {
res.RefreshToken = &authToken.RefreshToken.Token
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
}
return res, nil
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/email"
@@ -27,7 +28,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
return res, err
}
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
}
if params.ConfirmPassword != params.Password {
@@ -57,13 +58,13 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
if len(params.Roles) > 0 {
// check if roles exists
if !utils.IsValidRoles(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), params.Roles) {
if !utils.IsValidRoles(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), params.Roles) {
return res, fmt.Errorf(`invalid roles`)
} else {
inputRoles = params.Roles
}
} else {
inputRoles = envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)
inputRoles = envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)
}
user := models.User{
@@ -72,7 +73,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
user.Roles = strings.Join(inputRoles, ",")
password, _ := utils.EncryptPassword(params.Password)
password, _ := crypto.EncryptPassword(params.Password)
user.Password = &password
if params.GivenName != nil {
@@ -108,7 +109,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
}
user.SignupMethods = constants.SignupMethodBasicAuth
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
if envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
now := time.Now().Unix()
user.EmailVerifiedAt = &now
}
@@ -120,43 +121,50 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
userToReturn := user.AsAPIUser()
hostname := utils.GetHost(gc)
if !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
// insert verification request
verificationType := constants.VerificationTypeBasicAuthSignup
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname)
nonce, nonceHash, err := utils.GenerateNonce()
if err != nil {
log.Println(`error generating token`, err)
return res, err
}
verificationType := constants.VerificationTypeBasicAuthSignup
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname, nonceHash)
if err != nil {
return res, err
}
db.Provider.AddVerificationRequest(models.VerificationRequest{
Token: verificationToken,
Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: params.Email,
Nonce: nonce,
})
// exec it as go routin so that we can reduce the api latency
go func() {
email.SendVerificationMail(params.Email, verificationToken, hostname)
}()
go email.SendVerificationMail(params.Email, verificationToken, hostname)
res = &model.AuthResponse{
Message: `Verification email has been sent. Please check your inbox`,
User: userToReturn,
}
} else {
scope := []string{"openid", "email", "profile"}
authToken, err := token.CreateAuthToken(user, roles)
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
if err != nil {
return res, err
}
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
utils.SaveSessionInDB(user.ID, gc)
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash)
go utils.SaveSessionInDB(gc, user.ID)
expiresIn := int64(1800)
res = &model.AuthResponse{
Message: `Signed up successfully.`,
AccessToken: &authToken.AccessToken.Token,
ExpiresAt: &authToken.AccessToken.ExpiresAt,
ExpiresIn: &expiresIn,
User: userToReturn,
}
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
@@ -33,6 +34,66 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
return res, fmt.Errorf("unauthorized")
}
updatedData := envstore.EnvStoreObj.GetEnvStoreClone()
isJWTUpdated := false
algo := updatedData.StringEnv[constants.EnvKeyJwtType]
if params.JwtType != nil {
algo = *params.JwtType
if !crypto.IsHMACA(algo) && !crypto.IsECDSA(algo) && !crypto.IsRSA(algo) {
return res, fmt.Errorf("invalid jwt type")
}
updatedData.StringEnv[constants.EnvKeyJwtType] = algo
isJWTUpdated = true
}
if params.JwtSecret != nil || params.JwtPublicKey != nil || params.JwtPrivateKey != nil {
isJWTUpdated = true
}
if isJWTUpdated {
// check if jwt secret is provided
if crypto.IsHMACA(algo) {
if params.JwtSecret == nil {
return res, fmt.Errorf("jwt secret is required for HMAC algorithm")
}
}
if crypto.IsRSA(algo) {
if params.JwtPrivateKey == nil || params.JwtPublicKey == nil {
return res, fmt.Errorf("jwt private and public key is required for RSA (PKCS1) / ECDSA algorithm")
}
_, err = crypto.ParseRsaPrivateKeyFromPemStr(*params.JwtPrivateKey)
if err != nil {
return res, err
}
_, err := crypto.ParseRsaPublicKeyFromPemStr(*params.JwtPublicKey)
if err != nil {
return res, err
}
}
if crypto.IsECDSA(algo) {
if params.JwtPrivateKey == nil || params.JwtPublicKey == nil {
return res, fmt.Errorf("jwt private and public key is required for RSA (PKCS1) / ECDSA algorithm")
}
_, err = crypto.ParseEcdsaPrivateKeyFromPemStr(*params.JwtPrivateKey)
if err != nil {
return res, err
}
_, err := crypto.ParseEcdsaPublicKeyFromPemStr(*params.JwtPublicKey)
if err != nil {
return res, err
}
}
}
var data map[string]interface{}
byteData, err := json.Marshal(params)
if err != nil {
@@ -50,7 +111,7 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
return res, errors.New("admin secret and old admin secret are required for secret change")
}
if *params.OldAdminSecret != envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) {
if *params.OldAdminSecret != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) {
return res, errors.New("old admin secret is not correct")
}
@@ -61,7 +122,6 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
}
updatedData := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
for key, value := range data {
if value != nil {
fieldType := reflect.TypeOf(value).String()
@@ -116,9 +176,21 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
}
// Update local store
envstore.EnvInMemoryStoreObj.UpdateEnvStore(updatedData)
sessionstore.InitSession()
oauth.InitOAuth()
envstore.EnvStoreObj.UpdateEnvStore(updatedData)
jwk, err := crypto.GenerateJWKBasedOnEnv()
if err != nil {
return res, err
}
// updating jwk
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJWK, jwk)
err = sessionstore.InitSession()
if err != nil {
return res, err
}
err = oauth.InitOAuth()
if err != nil {
return res, err
}
// Fetch the current db store and update it
env, err := db.Provider.GetEnv()
@@ -127,14 +199,14 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
}
if params.AdminSecret != nil {
hashedKey, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
hashedKey, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
if err != nil {
return res, err
}
cookie.SetAdminCookie(gc, hashedKey)
}
encryptedConfig, err := utils.EncryptEnvData(updatedData)
encryptedConfig, err := crypto.EncryptEnvData(updatedData)
if err != nil {
return res, err
}

View File

@@ -9,9 +9,11 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/email"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/token"
@@ -27,7 +29,11 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
return res, err
}
claims, err := token.ValidateAccessToken(gc)
accessToken, err := token.GetAccessToken(gc)
if err != nil {
return res, err
}
claims, err := token.ValidateAccessToken(gc, accessToken)
if err != nil {
return res, err
}
@@ -37,8 +43,8 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
return res, fmt.Errorf("please enter at least one param to update")
}
userEmail := fmt.Sprintf("%v", claims["email"])
user, err := db.Provider.GetUserByEmail(userEmail)
userID := claims["sub"].(string)
user, err := db.Provider.GetUserByID(userID)
if err != nil {
return res, err
}
@@ -92,7 +98,7 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
return res, fmt.Errorf(`password and confirm password does not match`)
}
password, _ := utils.EncryptPassword(*params.NewPassword)
password, _ := crypto.EncryptPassword(*params.NewPassword)
user.Password = &password
}
@@ -107,38 +113,44 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
newEmail := strings.ToLower(*params.Email)
// check if user with new email exists
_, err := db.Provider.GetUserByEmail(newEmail)
// err = nil means user exists
if err == nil {
return res, fmt.Errorf("user with this email address already exists")
}
sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID))
cookie.DeleteCookie(gc)
// TODO figure out how to delete all user sessions
go sessionstore.DeleteAllUserSession(user.ID)
hostname := utils.GetHost(gc)
cookie.DeleteSession(gc)
user.Email = newEmail
user.EmailVerifiedAt = nil
hasEmailChanged = true
// insert verification request
verificationType := constants.VerificationTypeUpdateEmail
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname)
if err != nil {
log.Println(`error generating token`, err)
if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
hostname := utils.GetHost(gc)
user.EmailVerifiedAt = nil
hasEmailChanged = true
// insert verification request
nonce, nonceHash, err := utils.GenerateNonce()
if err != nil {
return res, err
}
verificationType := constants.VerificationTypeUpdateEmail
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname, nonceHash)
if err != nil {
log.Println(`error generating token`, err)
}
db.Provider.AddVerificationRequest(models.VerificationRequest{
Token: verificationToken,
Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: newEmail,
Nonce: nonce,
})
// exec it as go routin so that we can reduce the api latency
go email.SendVerificationMail(newEmail, verificationToken, hostname)
}
db.Provider.AddVerificationRequest(models.VerificationRequest{
Token: verificationToken,
Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: newEmail,
})
// exec it as go routin so that we can reduce the api latency
go func() {
email.SendVerificationMail(newEmail, verificationToken, hostname)
}()
}
_, err = db.Provider.UpdateUser(user)
if err != nil {
log.Println("error updating user:", err)

View File

@@ -8,7 +8,6 @@ import (
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/email"
@@ -95,15 +94,19 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
return res, fmt.Errorf("user with this email address already exists")
}
sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID))
cookie.DeleteCookie(gc)
// TODO figure out how to do this
go sessionstore.DeleteAllUserSession(user.ID)
hostname := utils.GetHost(gc)
user.Email = newEmail
user.EmailVerifiedAt = nil
// insert verification request
nonce, nonceHash, err := utils.GenerateNonce()
if err != nil {
return res, err
}
verificationType := constants.VerificationTypeUpdateEmail
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname)
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname, nonceHash)
if err != nil {
log.Println(`error generating token`, err)
}
@@ -112,12 +115,12 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: newEmail,
Nonce: nonce,
})
// exec it as go routin so that we can reduce the api latency
go func() {
email.SendVerificationMail(newEmail, verificationToken, hostname)
}()
go email.SendVerificationMail(newEmail, verificationToken, hostname)
}
rolesToSave := ""
@@ -128,7 +131,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
inputRoles = append(inputRoles, *item)
}
if !utils.IsValidRoles(inputRoles, append([]string{}, append(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...)) {
if !utils.IsValidRoles(inputRoles, append([]string{}, append(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...)) {
return res, fmt.Errorf("invalid list of roles")
}
@@ -136,8 +139,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
rolesToSave = strings.Join(inputRoles, ",")
}
sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID))
cookie.DeleteCookie(gc)
go sessionstore.DeleteAllUserSession(user.ID)
}
if rolesToSave != "" {

View File

@@ -24,16 +24,21 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
verificationRequest, err := db.Provider.GetVerificationRequestByToken(params.Token)
if err != nil {
return res, fmt.Errorf(`invalid token`)
return res, fmt.Errorf(`invalid token: %s`, err.Error())
}
// verify if token exists in db
claim, err := token.ParseJWTToken(params.Token)
hostname := utils.GetHost(gc)
encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce)
if err != nil {
return res, fmt.Errorf(`invalid token`)
return res, err
}
claim, err := token.ParseJWTToken(params.Token, hostname, encryptedNonce, verificationRequest.Email)
if err != nil {
return res, fmt.Errorf(`invalid token: %s`, err.Error())
}
user, err := db.Provider.GetUserByEmail(claim["email"].(string))
user, err := db.Provider.GetUserByEmail(claim["sub"].(string))
if err != nil {
return res, err
}
@@ -41,25 +46,35 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
// update email_verified_at in users table
now := time.Now().Unix()
user.EmailVerifiedAt = &now
db.Provider.UpdateUser(user)
// delete from verification table
db.Provider.DeleteVerificationRequest(verificationRequest)
roles := strings.Split(user.Roles, ",")
authToken, err := token.CreateAuthToken(user, roles)
user, err = db.Provider.UpdateUser(user)
if err != nil {
return res, err
}
// delete from verification table
err = db.Provider.DeleteVerificationRequest(verificationRequest)
if err != nil {
return res, err
}
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
cookie.SetCookie(gc, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
utils.SaveSessionInDB(user.ID, gc)
roles := strings.Split(user.Roles, ",")
scope := []string{"openid", "email", "profile"}
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
if err != nil {
return res, err
}
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash)
go utils.SaveSessionInDB(gc, user.ID)
expiresIn := int64(1800)
res = &model.AuthResponse{
Message: `Email verified successfully.`,
AccessToken: &authToken.AccessToken.Token,
ExpiresAt: &authToken.AccessToken.ExpiresAt,
IDToken: &authToken.IDToken.Token,
ExpiresIn: &expiresIn,
User: user.AsAPIUser(),
}
return res, nil
}

View File

@@ -20,6 +20,13 @@ func InitRouter() *gin.Engine {
router.GET("/oauth_login/:oauth_provider", handlers.OAuthLoginHandler())
router.GET("/oauth_callback/:oauth_provider", handlers.OAuthCallbackHandler())
router.GET("/verify_email", handlers.VerifyEmailHandler())
// OPEN ID routes
router.GET("/.well-known/openid-configuration", handlers.OpenIDConfigurationHandler())
router.GET("/.well-known/jwks.json", handlers.JWKsHandler())
router.GET("/authorize", handlers.AuthorizeHandler())
router.GET("/userinfo", handlers.UserInfoHandler())
router.GET("/logout", handlers.LogoutHandler())
router.POST("/oauth/token", handlers.TokenHandler())
router.LoadHTMLGlob("templates/*")
// login page app related routes.

View File

@@ -1,112 +1,74 @@
package sessionstore
import (
"strings"
"sync"
)
// InMemoryStore is a simple in-memory store for sessions.
type InMemoryStore struct {
mutex sync.Mutex
store map[string]map[string]string
socialLoginState map[string]string
}
// AddUserSession adds a user session to the in-memory store.
func (c *InMemoryStore) AddUserSession(userId, accessToken, refreshToken string) {
c.mutex.Lock()
defer c.mutex.Unlock()
// delete sessions > 500 // not recommended for production
if len(c.store) >= 500 {
c.store = map[string]map[string]string{}
}
// check if entry exists in map
_, exists := c.store[userId]
if exists {
tempMap := c.store[userId]
tempMap[accessToken] = refreshToken
c.store[userId] = tempMap
} else {
tempMap := map[string]string{
accessToken: refreshToken,
}
c.store[userId] = tempMap
}
}
// DeleteAllUserSession deletes all the user sessions from in-memory store.
func (c *InMemoryStore) DeleteAllUserSession(userId string) {
c.mutex.Lock()
defer c.mutex.Unlock()
delete(c.store, userId)
}
// DeleteUserSession deletes the particular user session from in-memory store.
func (c *InMemoryStore) DeleteUserSession(userId, accessToken string) {
c.mutex.Lock()
defer c.mutex.Unlock()
delete(c.store[userId], accessToken)
mutex sync.Mutex
sessionStore map[string]map[string]string
stateStore map[string]string
}
// ClearStore clears the in-memory store.
func (c *InMemoryStore) ClearStore() {
c.mutex.Lock()
defer c.mutex.Unlock()
c.store = map[string]map[string]string{}
}
// GetUserSession returns the user session token from the in-memory store.
func (c *InMemoryStore) GetUserSession(userId, accessToken string) string {
// c.mutex.Lock()
// defer c.mutex.Unlock()
token := ""
if sessionMap, ok := c.store[userId]; ok {
if val, ok := sessionMap[accessToken]; ok {
token = val
}
}
return token
c.sessionStore = map[string]map[string]string{}
}
// GetUserSessions returns all the user session token from the in-memory store.
func (c *InMemoryStore) GetUserSessions(userId string) map[string]string {
// c.mutex.Lock()
// defer c.mutex.Unlock()
sessionMap, ok := c.store[userId]
if !ok {
return nil
res := map[string]string{}
for k, v := range c.stateStore {
split := strings.Split(v, "@")
if split[1] == userId {
res[k] = split[0]
}
}
return sessionMap
return res
}
// SetSocialLoginState sets the social login state in the in-memory store.
func (c *InMemoryStore) SetSocialLoginState(key, state string) {
// DeleteAllUserSession deletes all the user sessions from in-memory store.
func (c *InMemoryStore) DeleteAllUserSession(userId string) {
// c.mutex.Lock()
// defer c.mutex.Unlock()
sessions := GetUserSessions(userId)
for k := range sessions {
RemoveState(k)
}
}
// SetState sets the state in the in-memory store.
func (c *InMemoryStore) SetState(key, state string) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.socialLoginState[key] = state
c.stateStore[key] = state
}
// GetSocialLoginState gets the social login state from the in-memory store.
func (c *InMemoryStore) GetSocialLoginState(key string) string {
// GetState gets the state from the in-memory store.
func (c *InMemoryStore) GetState(key string) string {
c.mutex.Lock()
defer c.mutex.Unlock()
state := ""
if stateVal, ok := c.socialLoginState[key]; ok {
if stateVal, ok := c.stateStore[key]; ok {
state = stateVal
}
return state
}
// RemoveSocialLoginState removes the social login state from the in-memory store.
func (c *InMemoryStore) RemoveSocialLoginState(key string) {
// RemoveState removes the state from the in-memory store.
func (c *InMemoryStore) RemoveState(key string) {
c.mutex.Lock()
defer c.mutex.Unlock()
delete(c.socialLoginState, key)
delete(c.stateStore, key)
}

View File

@@ -0,0 +1,18 @@
package sessionstore
import (
"context"
"time"
"github.com/go-redis/redis/v8"
)
type RedisSessionClient interface {
HMSet(ctx context.Context, key string, values ...interface{}) *redis.BoolCmd
Del(ctx context.Context, keys ...string) *redis.IntCmd
HDel(ctx context.Context, key string, fields ...string) *redis.IntCmd
HMGet(ctx context.Context, key string, fields ...string) *redis.SliceCmd
HGetAll(ctx context.Context, key string) *redis.StringStringMapCmd
Set(ctx context.Context, key string, value interface{}, expiration time.Duration) *redis.StatusCmd
Get(ctx context.Context, key string) *redis.StringCmd
}

View File

@@ -2,41 +2,13 @@ package sessionstore
import (
"context"
"fmt"
"log"
"github.com/go-redis/redis/v8"
"strings"
)
type RedisStore struct {
ctx context.Context
store *redis.Client
}
// AddUserSession adds the user session to redis
func (c *RedisStore) AddUserSession(userId, accessToken, refreshToken string) {
err := c.store.HMSet(c.ctx, "authorizer_"+userId, map[string]string{
accessToken: refreshToken,
}).Err()
if err != nil {
log.Fatalln("Error saving redis token:", err)
}
}
// DeleteAllUserSession deletes all the user session from redis
func (c *RedisStore) DeleteAllUserSession(userId string) {
err := c.store.Del(c.ctx, "authorizer_"+userId).Err()
if err != nil {
log.Fatalln("Error deleting redis token:", err)
}
}
// DeleteUserSession deletes the particular user session from redis
func (c *RedisStore) DeleteUserSession(userId, accessToken string) {
err := c.store.HDel(c.ctx, "authorizer_"+userId, accessToken).Err()
if err != nil {
log.Fatalln("Error deleting redis token:", err)
}
store RedisSessionClient
}
// ClearStore clears the redis store for authorizer related tokens
@@ -47,41 +19,49 @@ func (c *RedisStore) ClearStore() {
}
}
// GetUserSession returns the user session token from the redis store.
func (c *RedisStore) GetUserSession(userId, accessToken string) string {
token := ""
res, err := c.store.HMGet(c.ctx, "authorizer_"+userId, accessToken).Result()
if err != nil {
log.Println("error getting token from redis store:", err)
}
if len(res) > 0 && res[0] != nil {
token = fmt.Sprintf("%v", res[0])
}
return token
}
// GetUserSessions returns all the user session token from the redis store.
func (c *RedisStore) GetUserSessions(userID string) map[string]string {
res, err := c.store.HGetAll(c.ctx, "authorizer_"+userID).Result()
data, err := c.store.HGetAll(c.ctx, "*").Result()
if err != nil {
log.Println("error getting token from redis store:", err)
}
res := map[string]string{}
for k, v := range data {
split := strings.Split(v, "@")
if split[1] == userID {
res[k] = split[0]
}
}
return res
}
// SetSocialLoginState sets the social login state in redis store.
func (c *RedisStore) SetSocialLoginState(key, state string) {
err := c.store.Set(c.ctx, key, state, 0).Err()
// DeleteAllUserSession deletes all the user session from redis
func (c *RedisStore) DeleteAllUserSession(userId string) {
sessions := GetUserSessions(userId)
for k, v := range sessions {
if k == "token" {
err := c.store.Del(c.ctx, v)
if err != nil {
log.Println("Error deleting redis token:", err)
}
}
}
}
// SetState sets the state in redis store.
func (c *RedisStore) SetState(key, value string) {
err := c.store.Set(c.ctx, "authorizer_"+key, value, 0).Err()
if err != nil {
log.Fatalln("Error saving redis token:", err)
}
}
// GetSocialLoginState gets the social login state from redis store.
func (c *RedisStore) GetSocialLoginState(key string) string {
// GetState gets the state from redis store.
func (c *RedisStore) GetState(key string) string {
state := ""
state, err := c.store.Get(c.ctx, key).Result()
state, err := c.store.Get(c.ctx, "authorizer_"+key).Result()
if err != nil {
log.Println("error getting token from redis store:", err)
}
@@ -89,9 +69,9 @@ func (c *RedisStore) GetSocialLoginState(key string) string {
return state
}
// RemoveSocialLoginState removes the social login state from redis store.
func (c *RedisStore) RemoveSocialLoginState(key string) {
err := c.store.Del(c.ctx, key).Err()
// RemoveState removes the state from redis store.
func (c *RedisStore) RemoveState(key string) {
err := c.store.Del(c.ctx, "authorizer_"+key).Err()
if err != nil {
log.Fatalln("Error deleting redis token:", err)
}

View File

@@ -3,6 +3,7 @@ package sessionstore
import (
"context"
"log"
"strings"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
@@ -21,26 +22,6 @@ type SessionStore struct {
// reference to various session store instances
var SessionStoreObj SessionStore
// SetUserSession sets the user session in the session store
func SetUserSession(userId, fingerprint, refreshToken string) {
if SessionStoreObj.RedisMemoryStoreObj != nil {
SessionStoreObj.RedisMemoryStoreObj.AddUserSession(userId, fingerprint, refreshToken)
}
if SessionStoreObj.InMemoryStoreObj != nil {
SessionStoreObj.InMemoryStoreObj.AddUserSession(userId, fingerprint, refreshToken)
}
}
// DeleteUserSession deletes the particular user session from the session store
func DeleteUserSession(userId, fingerprint string) {
if SessionStoreObj.RedisMemoryStoreObj != nil {
SessionStoreObj.RedisMemoryStoreObj.DeleteUserSession(userId, fingerprint)
}
if SessionStoreObj.InMemoryStoreObj != nil {
SessionStoreObj.InMemoryStoreObj.DeleteUserSession(userId, fingerprint)
}
}
// DeleteAllSessions deletes all the sessions from the session store
func DeleteAllUserSession(userId string) {
if SessionStoreObj.RedisMemoryStoreObj != nil {
@@ -51,18 +32,6 @@ func DeleteAllUserSession(userId string) {
}
}
// GetUserSession returns the user session from the session store
func GetUserSession(userId, fingerprint string) string {
if SessionStoreObj.RedisMemoryStoreObj != nil {
return SessionStoreObj.RedisMemoryStoreObj.GetUserSession(userId, fingerprint)
}
if SessionStoreObj.InMemoryStoreObj != nil {
return SessionStoreObj.InMemoryStoreObj.GetUserSession(userId, fingerprint)
}
return ""
}
// GetUserSessions returns all the user sessions from the session store
func GetUserSessions(userId string) map[string]string {
if SessionStoreObj.RedisMemoryStoreObj != nil {
@@ -85,62 +54,97 @@ func ClearStore() {
}
}
// SetSocialLoginState sets the social login state in the session store
func SetSocailLoginState(key, state string) {
// SetState sets the login state (key, value form) in the session store
func SetState(key, state string) {
if SessionStoreObj.RedisMemoryStoreObj != nil {
SessionStoreObj.RedisMemoryStoreObj.SetSocialLoginState(key, state)
SessionStoreObj.RedisMemoryStoreObj.SetState(key, state)
}
if SessionStoreObj.InMemoryStoreObj != nil {
SessionStoreObj.InMemoryStoreObj.SetSocialLoginState(key, state)
SessionStoreObj.InMemoryStoreObj.SetState(key, state)
}
}
// GetSocialLoginState returns the social login state from the session store
func GetSocailLoginState(key string) string {
// GetState returns the state from the session store
func GetState(key string) string {
if SessionStoreObj.RedisMemoryStoreObj != nil {
return SessionStoreObj.RedisMemoryStoreObj.GetSocialLoginState(key)
return SessionStoreObj.RedisMemoryStoreObj.GetState(key)
}
if SessionStoreObj.InMemoryStoreObj != nil {
return SessionStoreObj.InMemoryStoreObj.GetSocialLoginState(key)
return SessionStoreObj.InMemoryStoreObj.GetState(key)
}
return ""
}
// RemoveSocialLoginState removes the social login state from the session store
func RemoveSocialLoginState(key string) {
// RemoveState removes the social login state from the session store
func RemoveState(key string) {
if SessionStoreObj.RedisMemoryStoreObj != nil {
SessionStoreObj.RedisMemoryStoreObj.RemoveSocialLoginState(key)
SessionStoreObj.RedisMemoryStoreObj.RemoveState(key)
}
if SessionStoreObj.InMemoryStoreObj != nil {
SessionStoreObj.InMemoryStoreObj.RemoveSocialLoginState(key)
SessionStoreObj.InMemoryStoreObj.RemoveState(key)
}
}
// InitializeSessionStore initializes the SessionStoreObj based on environment variables
func InitSession() {
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL) != "" {
func InitSession() error {
if envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL) != "" {
log.Println("using redis store to save sessions")
opt, err := redis.ParseURL(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL))
if err != nil {
log.Fatalln("Error parsing redis url:", err)
redisURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL)
redisURLHostPortsList := strings.Split(redisURL, ",")
if len(redisURLHostPortsList) > 1 {
opt, err := redis.ParseURL(redisURLHostPortsList[0])
if err != nil {
return err
}
urls := []string{opt.Addr}
urlList := redisURLHostPortsList[1:]
urls = append(urls, urlList...)
clusterOpt := &redis.ClusterOptions{Addrs: urls}
rdb := redis.NewClusterClient(clusterOpt)
ctx := context.Background()
_, err = rdb.Ping(ctx).Result()
if err != nil {
return err
}
SessionStoreObj.RedisMemoryStoreObj = &RedisStore{
ctx: ctx,
store: rdb,
}
// return on successful initialization
return nil
}
opt, err := redis.ParseURL(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyRedisURL))
if err != nil {
return err
}
rdb := redis.NewClient(opt)
ctx := context.Background()
_, err = rdb.Ping(ctx).Result()
if err != nil {
log.Fatalln("Error connecting to redis server", err)
return err
}
SessionStoreObj.RedisMemoryStoreObj = &RedisStore{
ctx: ctx,
store: rdb,
}
} else {
SessionStoreObj.InMemoryStoreObj = &InMemoryStore{
store: map[string]map[string]string{},
socialLoginState: map[string]string{},
}
// return on successful initialization
return nil
}
// if redis url is not set use in memory store
SessionStoreObj.InMemoryStoreObj = &InMemoryStore{
sessionStore: map[string]map[string]string{},
stateStore: map[string]string{},
}
return nil
}

View File

@@ -21,7 +21,7 @@ func adminLoginTests(t *testing.T, s TestSetup) {
assert.NotNil(t, err)
_, err = resolvers.AdminLoginResolver(ctx, model.AdminLoginInput{
AdminSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret),
AdminSecret: envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret),
})
assert.Nil(t, err)

View File

@@ -5,9 +5,9 @@ import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
@@ -18,9 +18,9 @@ func adminLogoutTests(t *testing.T, s TestSetup) {
_, err := resolvers.AdminLogoutResolver(ctx)
assert.NotNil(t, err)
h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
assert.Nil(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
_, err = resolvers.AdminLogoutResolver(ctx)
assert.Nil(t, err)

View File

@@ -5,9 +5,9 @@ import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
@@ -18,9 +18,9 @@ func adminSessionTests(t *testing.T, s TestSetup) {
_, err := resolvers.AdminSessionResolver(ctx)
assert.NotNil(t, err)
h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
assert.Nil(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
_, err = resolvers.AdminSessionResolver(ctx)
assert.Nil(t, err)

View File

@@ -20,7 +20,7 @@ func adminSignupTests(t *testing.T, s TestSetup) {
assert.NotNil(t, err)
// reset env for test to pass
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, "")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, "")
_, err = resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{
AdminSecret: "admin123",

View File

@@ -5,10 +5,10 @@ import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
@@ -28,9 +28,9 @@ func deleteUserTest(t *testing.T, s TestSetup) {
})
assert.NotNil(t, err, "unauthorized")
h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
assert.Nil(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
_, err = resolvers.DeleteUserResolver(ctx, model.DeleteUserInput{
Email: email,

View File

@@ -10,15 +10,15 @@ import (
)
func TestEnvs(t *testing.T) {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample")
env.InitEnv()
store := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample")
env.InitAllEnv()
store := envstore.EnvStoreObj.GetEnvStoreClone()
assert.Equal(t, store.StringEnv[constants.EnvKeyEnv], "production")
assert.False(t, store.BoolEnv[constants.EnvKeyDisableEmailVerification])
assert.False(t, store.BoolEnv[constants.EnvKeyDisableMagicLinkLogin])
assert.False(t, store.BoolEnv[constants.EnvKeyDisableBasicAuthentication])
assert.Equal(t, store.StringEnv[constants.EnvKeyJwtType], "HS256")
assert.Equal(t, store.StringEnv[constants.EnvKeyJwtType], "RS256")
assert.Equal(t, store.StringEnv[constants.EnvKeyJwtRoleClaim], "role")
assert.EqualValues(t, store.SliceEnv[constants.EnvKeyRoles], []string{"user"})
assert.EqualValues(t, store.SliceEnv[constants.EnvKeyDefaultRoles], []string{"user"})

View File

@@ -5,9 +5,9 @@ import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
@@ -18,12 +18,12 @@ func envTests(t *testing.T, s TestSetup) {
_, err := resolvers.EnvResolver(ctx)
assert.NotNil(t, err)
h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
assert.Nil(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
res, err := resolvers.EnvResolver(ctx)
assert.Nil(t, err)
assert.Equal(t, *res.AdminSecret, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
assert.Equal(t, *res.AdminSecret, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
})
}

View File

@@ -1,38 +0,0 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/token"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)
func isValidJWTTests(t *testing.T, s TestSetup) {
t.Helper()
_, ctx := createContext(s)
expiredToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhbGxvd2VkX3JvbGVzIjpbIiJdLCJiaXJ0aGRhdGUiOm51bGwsImNyZWF0ZWRfYXQiOjAsImVtYWlsIjoiam9obi5kb2VAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJleHAiOjE2NDI5NjEwMTEsImV4dHJhIjp7IngtZXh0cmEtaWQiOiJkMmNhMjQwNy05MzZmLTQwYzQtOTQ2NS05Y2M5MWYxZTJhNDQifSwiZmFtaWx5X25hbWUiOm51bGwsImdlbmRlciI6bnVsbCwiZ2l2ZW5fbmFtZSI6bnVsbCwiaWF0IjoxNjQyOTYwOTgxLCJpZCI6ImQyY2EyNDA3LTkzNmYtNDBjNC05NDY1LTljYzkxZjFlMmE0NCIsIm1pZGRsZV9uYW1lIjpudWxsLCJuaWNrbmFtZSI6bnVsbCwicGhvbmVfbnVtYmVyIjpudWxsLCJwaG9uZV9udW1iZXJfdmVyaWZpZWQiOmZhbHNlLCJwaWN0dXJlIjpudWxsLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJqb2huLmRvZUBnbWFpbC5jb20iLCJyb2xlIjpbXSwic2lnbnVwX21ldGhvZHMiOiIiLCJ0b2tlbl90eXBlIjoiYWNjZXNzX3Rva2VuIiwidXBkYXRlZF9hdCI6MH0.FrdyeOC5e8uU1SowGj0omFJuwRnh4BrEk89S_fbEkzs"
t.Run(`should fail for invalid jwt`, func(t *testing.T) {
_, err := resolvers.IsValidJwtResolver(ctx, &model.IsValidJWTQueryInput{
Jwt: &expiredToken,
})
assert.NotNil(t, err)
})
t.Run(`should pass with valid jwt`, func(t *testing.T) {
authToken, err := token.CreateAuthToken(models.User{
ID: uuid.New().String(),
Email: "john.doe@gmail.com",
}, []string{})
assert.Nil(t, err)
res, err := resolvers.IsValidJwtResolver(ctx, &model.IsValidJWTQueryInput{
Jwt: &authToken.AccessToken.Token,
})
assert.Nil(t, err)
assert.True(t, res.Valid)
})
}

View File

@@ -5,31 +5,42 @@ import (
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/token"
"github.com/golang-jwt/jwt"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)
func TestJwt(t *testing.T) {
// persist older data till test is done and then reset it
jwtType := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
publicKey := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)
privateKey := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPrivateKey)
clientID := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID)
nonce := uuid.New().String()
hostname := "localhost"
subject := "test"
claims := jwt.MapClaims{
"exp": time.Now().Add(time.Minute * 30).Unix(),
"iat": time.Now().Unix(),
"email": "test@yopmail.com",
"sub": subject,
"aud": clientID,
"nonce": nonce,
"iss": hostname,
}
// persist older data till test is done and then reset it
jwtType := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
jwtSecret := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret)
t.Run("invalid jwt type", func(t *testing.T) {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "invalid")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "invalid")
token, err := token.SignJWTToken(claims)
assert.Error(t, err, "unsupported signing method")
assert.Empty(t, token)
})
t.Run("expired jwt token", func(t *testing.T) {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS256")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS256")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtSecret, "test")
expiredClaims := jwt.MapClaims{
"exp": time.Now().Add(-time.Minute * 30).Unix(),
"iat": time.Now().Unix(),
@@ -37,66 +48,77 @@ func TestJwt(t *testing.T) {
}
jwtToken, err := token.SignJWTToken(expiredClaims)
assert.NoError(t, err)
_, err = token.ParseJWTToken(jwtToken)
_, err = token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.Error(t, err, err.Error(), "Token is expired")
})
t.Run("HMAC algorithms", func(t *testing.T) {
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtSecret, "test")
t.Run("HS256", func(t *testing.T) {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS256")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS256")
jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err)
assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"])
})
t.Run("HS384", func(t *testing.T) {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS384")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS384")
jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err)
assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"])
})
t.Run("HS512", func(t *testing.T) {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS512")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "HS512")
jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err)
assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"])
})
})
t.Run("RSA algorithms", func(t *testing.T) {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, "-----BEGIN RSA PRIVATE KEY-----\nMIICWgIBAAKBgHUQac/v0f3c8m4L9BMWfxBiEzkdV5CoaqfxhO5IwAX/1cs0WceN\njM7g/qzC7YmEOSiYqupiRtsyn6riz0xT/VUg4uv1uZ/muC6EVfOjR5Ack3Brquql\nD+oMxN4CeA0Wzp2dEV4N3Gv7wWHdhg9ZSc4g6+ZUdlkhIPfeO9RNK9pPAgMBAAEC\ngYBqLrIbp0dNQn0vbm48ZhppDNys4L2NfAYKQZs23Aw5JN6Si/CnffBrsk+u+ryl\nEKcb+KaHJQ9qQdfsFAC+FizhMQy0Dq9yw6shnqHX+paB6E6z2/vX8ToPzJRwxBY3\nyuaetCEpSXR7pQEd5YWDTUH7qYnb9FObD+umhVvmlsTHCQJBALagPmexu0DvMXKZ\nWdplik6eXg9lptiuj5MYqitEUyzU9E9HNeHKlZM7szGeWG3jNduoKcyo4M0Flvt9\ncP+soVUCQQCkGOQ5Y3/GoZmclKWMVwqGdmL6wEjhNfg4PRfgUalHBif9Q1KnM8FP\nAvIqIH8bttRfyT185WmaM2gml0ApwF0TAkBVil9QoK4t7xvBKtUsd809n+481gc9\njR4Q70edtoYjBKhejeNOHF7NNPRtNFcFOZybg3v4sc2CGrEqoQoRp+F1AkBeLmMe\nhPrbF/jAI5h4WaSS0/OvExlBGOaj8Hx5pKTRPLlK5I7VpCC4pmoyv3/0ehSd/TQr\nMMhRVlvaeki7Lcq9AkBravJUadVCAIsB6oh03mo8gUFFFqXDyEl6BiJYqrjCQ5wd\nAQYJGbqQvgjPxN9+PTPldDNi6KVXntSg5gF/dA+Z\n-----END RSA PRIVATE KEY-----")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, "-----BEGIN PUBLIC KEY-----\nMIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHUQac/v0f3c8m4L9BMWfxBiEzkd\nV5CoaqfxhO5IwAX/1cs0WceNjM7g/qzC7YmEOSiYqupiRtsyn6riz0xT/VUg4uv1\nuZ/muC6EVfOjR5Ack3BrquqlD+oMxN4CeA0Wzp2dEV4N3Gv7wWHdhg9ZSc4g6+ZU\ndlkhIPfeO9RNK9pPAgMBAAE=\n-----END PUBLIC KEY-----")
t.Run("RS256", func(t *testing.T) {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "RS256")
_, privateKey, publickKey, _, err := crypto.NewRSAKey("RS256", clientID)
assert.NoError(t, err)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "RS256")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publickKey)
jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err)
assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"])
})
t.Run("RS384", func(t *testing.T) {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "RS384")
_, privateKey, publickKey, _, err := crypto.NewRSAKey("RS384", clientID)
assert.NoError(t, err)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "RS384")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publickKey)
jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err)
assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"])
})
t.Run("RS512", func(t *testing.T) {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "RS512")
_, privateKey, publickKey, _, err := crypto.NewRSAKey("RS512", clientID)
assert.NoError(t, err)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "RS512")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publickKey)
jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err)
assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"])
})
@@ -104,40 +126,47 @@ func TestJwt(t *testing.T) {
t.Run("ECDSA algorithms", func(t *testing.T) {
t.Run("ES256", func(t *testing.T) {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, "-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2\nOF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r\n1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G\n-----END PRIVATE KEY-----")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9\nq9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==\n-----END PUBLIC KEY-----")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "ES256")
_, privateKey, publickKey, _, err := crypto.NewECDSAKey("ES256", clientID)
assert.NoError(t, err)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "ES256")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publickKey)
jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err)
assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"])
})
t.Run("ES384", func(t *testing.T) {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, "-----BEGIN PRIVATE KEY-----\nMIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCAHpFQ62QnGCEvYh/p\nE9QmR1C9aLcDItRbslbmhen/h1tt8AyMhskeenT+rAyyPhGhZANiAAQLW5ZJePZz\nMIPAxMtZXkEWbDF0zo9f2n4+T1h/2sh/fviblc/VTyrv10GEtIi5qiOy85Pf1RRw\n8lE5IPUWpgu553SteKigiKLUPeNpbqmYZUkWGh3MLfVzLmx85ii2vMU=\n-----END PRIVATE KEY-----")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, "-----BEGIN PUBLIC KEY-----\nMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEC1uWSXj2czCDwMTLWV5BFmwxdM6PX9p+\nPk9Yf9rIf374m5XP1U8q79dBhLSIuaojsvOT39UUcPJROSD1FqYLued0rXiooIii\n1D3jaW6pmGVJFhodzC31cy5sfOYotrzF\n-----END PUBLIC KEY-----")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "ES384")
_, privateKey, publickKey, _, err := crypto.NewECDSAKey("ES384", clientID)
assert.NoError(t, err)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "ES384")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publickKey)
jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err)
assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"])
})
t.Run("ES512", func(t *testing.T) {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, "-----BEGIN PRIVATE KEY-----\nMIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIBiyAa7aRHFDCh2qga\n9sTUGINE5jHAFnmM8xWeT/uni5I4tNqhV5Xx0pDrmCV9mbroFtfEa0XVfKuMAxxf\nZ6LM/yKhgYkDgYYABAGBzgdnP798FsLuWYTDDQA7c0r3BVk8NnRUSexpQUsRilPN\nv3SchO0lRw9Ru86x1khnVDx+duq4BiDFcvlSAcyjLACJvjvoyTLJiA+TQFdmrear\njMiZNE25pT2yWP1NUndJxPcvVtfBW48kPOmvkY4WlqP5bAwCXwbsKrCgk6xbsp12\new==\n-----END PRIVATE KEY-----")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, "-----BEGIN PUBLIC KEY-----\nMIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBgc4HZz+/fBbC7lmEww0AO3NK9wVZ\nPDZ0VEnsaUFLEYpTzb90nITtJUcPUbvOsdZIZ1Q8fnbquAYgxXL5UgHMoywAib47\n6MkyyYgPk0BXZq3mq4zImTRNuaU9slj9TVJ3ScT3L1bXwVuPJDzpr5GOFpaj+WwM\nAl8G7CqwoJOsW7Kddns=\n-----END PUBLIC KEY-----")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "ES512")
_, privateKey, publickKey, _, err := crypto.NewECDSAKey("ES512", clientID)
assert.NoError(t, err)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, "ES512")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publickKey)
jwtToken, err := token.SignJWTToken(claims)
assert.NoError(t, err)
assert.NotEmpty(t, jwtToken)
c, err := token.ParseJWTToken(jwtToken)
c, err := token.ParseJWTToken(jwtToken, hostname, nonce, subject)
assert.NoError(t, err)
assert.Equal(t, c["email"].(string), claims["email"])
})
})
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, jwtType)
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtSecret, jwtSecret)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtType, jwtType)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPublicKey, publicKey)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyJwtPrivateKey, privateKey)
}

View File

@@ -5,14 +5,17 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
func loginTests(t *testing.T, s TestSetup) {
t.Helper()
t.Run(`should login`, func(t *testing.T) {
t.Logf("=> is enabled: %v", envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification))
_, ctx := createContext(s)
email := "login." + s.TestInfo.Email
_, err := resolvers.SignupResolver(ctx, model.SignUpInput{
@@ -21,18 +24,23 @@ func loginTests(t *testing.T, s TestSetup) {
ConfirmPassword: s.TestInfo.Password,
})
_, err = resolvers.LoginResolver(ctx, model.LoginInput{
res, err := resolvers.LoginResolver(ctx, model.LoginInput{
Email: email,
Password: s.TestInfo.Password,
})
assert.NotNil(t, err, "should fail because email is not verified")
assert.Nil(t, res)
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(email, constants.VerificationTypeBasicAuthSignup)
resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
n, err := utils.EncryptNonce(verificationRequest.Nonce)
assert.NoError(t, err)
assert.NotEmpty(t, n)
assert.NotNil(t, verificationRequest)
res, err = resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
assert.NoError(t, err)
assert.NotNil(t, res)
_, err = resolvers.LoginResolver(ctx, model.LoginInput{
Email: email,
Password: s.TestInfo.Password,

View File

@@ -2,7 +2,6 @@ package test
import (
"fmt"
"net/url"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
@@ -11,7 +10,6 @@ import (
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
@@ -30,18 +28,15 @@ func logoutTests(t *testing.T, s TestSetup) {
Token: verificationRequest.Token,
})
sessions := sessionstore.GetUserSessions(verifyRes.User.ID)
fingerPrint := ""
refreshToken := ""
for key, val := range sessions {
fingerPrint = key
refreshToken = val
}
fingerPrintHash, _ := utils.EncryptAES([]byte(fingerPrint))
token := *verifyRes.AccessToken
cookie := fmt.Sprintf("%s=%s;%s=%s;%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", url.QueryEscape(string(fingerPrintHash)), envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token)
sessions := sessionstore.GetUserSessions(verifyRes.User.ID)
cookie := ""
// set all they keys in cookie one of them should be session cookie
for key := range sessions {
if key != token {
cookie += fmt.Sprintf("%s=%s;", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", key)
}
}
req.Header.Set("Cookie", cookie)
_, err = resolvers.LogoutResolver(ctx)

View File

@@ -1,12 +1,11 @@
package test
import (
"fmt"
"context"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
@@ -27,12 +26,13 @@ func magicLinkLoginTests(t *testing.T, s TestSetup) {
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
token := *verifyRes.AccessToken
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token))
assert.NoError(t, err)
assert.NotNil(t, verifyRes.AccessToken)
s.GinContext.Request.Header.Set("Authorization", "Bearer "+*verifyRes.AccessToken)
ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)
_, err = resolvers.ProfileResolver(ctx)
assert.Nil(t, err)
s.GinContext.Request.Header.Set("Authorization", "")
cleanData(email)
})
}

View File

@@ -1,12 +1,11 @@
package test
import (
"fmt"
"context"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
@@ -14,7 +13,7 @@ import (
func profileTests(t *testing.T, s TestSetup) {
t.Helper()
t.Run(`should get profile only with token`, func(t *testing.T) {
t.Run(`should get profile only access_token token`, func(t *testing.T) {
req, ctx := createContext(s)
email := "profile." + s.TestInfo.Email
@@ -31,11 +30,14 @@ func profileTests(t *testing.T, s TestSetup) {
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
assert.NoError(t, err)
assert.NotNil(t, verifyRes.AccessToken)
token := *verifyRes.AccessToken
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token))
s.GinContext.Request.Header.Set("Authorization", "Bearer "+*verifyRes.AccessToken)
ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)
profileRes, err := resolvers.ProfileResolver(ctx)
assert.Nil(t, err)
s.GinContext.Request.Header.Set("Authorization", "")
newEmail := *&profileRes.Email
assert.Equal(t, email, newEmail, "emails should be equal")

View File

@@ -1,7 +1,6 @@
package test
import (
"log"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
@@ -12,29 +11,31 @@ import (
func TestResolvers(t *testing.T) {
databases := map[string]string{
constants.DbTypeSqlite: "../../data.db",
constants.DbTypeArangodb: "http://localhost:8529",
constants.DbTypeMongodb: "mongodb://localhost:27017",
constants.DbTypeSqlite: "../../data.db",
// constants.DbTypeArangodb: "http://localhost:8529",
// constants.DbTypeMongodb: "mongodb://localhost:27017",
}
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, "test")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyVersion, "test")
for dbType, dbURL := range databases {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL)
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType)
s := testSetup()
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL)
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType)
defer s.Server.Close()
db.InitDB()
err := db.InitDB()
if err != nil {
t.Errorf("Error initializing database: %s", err)
}
// clean the persisted config for test to use fresh config
envData, err := db.Provider.GetEnv()
log.Println("=> envData:", envstore.EnvInMemoryStoreObj.GetEnvStoreClone())
if err == nil {
envData.EnvData = ""
db.Provider.UpdateEnv(envData)
}
env.PersistEnv()
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnv, "test")
envstore.EnvStoreObj.UpdateEnvVariable(constants.BoolStoreIdentifier, constants.EnvKeyIsProd, false)
t.Run("should pass tests for "+dbType, func(t *testing.T) {
// admin tests
adminSignupTests(t, s)
@@ -61,7 +62,6 @@ func TestResolvers(t *testing.T) {
magicLinkLoginTests(t, s)
logoutTests(t, s)
metaTests(t, s)
isValidJWTTests(t, s)
})
}
}

View File

@@ -2,7 +2,7 @@ package test
import (
"fmt"
"net/url"
"strings"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
@@ -11,7 +11,6 @@ import (
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
@@ -36,17 +35,15 @@ func sessionTests(t *testing.T, s TestSetup) {
})
sessions := sessionstore.GetUserSessions(verifyRes.User.ID)
fingerPrint := ""
refreshToken := ""
for key, val := range sessions {
fingerPrint = key
refreshToken = val
}
fingerPrintHash, _ := utils.EncryptAES([]byte(fingerPrint))
cookie := ""
token := *verifyRes.AccessToken
cookie := fmt.Sprintf("%s=%s;%s=%s;%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", url.QueryEscape(string(fingerPrintHash)), envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token)
// set all they keys in cookie one of them should be session cookie
for key := range sessions {
if key != token {
cookie += fmt.Sprintf("%s=%s;", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+"_session", key)
}
}
cookie = strings.TrimSuffix(cookie, ";")
req.Header.Set("Cookie", cookie)

View File

@@ -71,14 +71,16 @@ func testSetup() TestSetup {
Password: "test",
}
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpHost, "smtp.yopmail.com")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPort, "2525")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpUsername, "lakhan@yopmail.com")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPassword, "test")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySenderEmail, "info@yopmail.com")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyProtectedRoles, []string{"admin"})
env.InitEnv()
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample")
env.InitRequiredEnv()
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpHost, "smtp.yopmail.com")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPort, "2525")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpUsername, "lakhan@yopmail.com")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPassword, "test")
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySenderEmail, "info@yopmail.com")
envstore.EnvStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyProtectedRoles, []string{"admin"})
db.InitDB()
env.InitAllEnv()
sessionstore.InitSession()
w := httptest.NewRecorder()

View File

@@ -5,10 +5,10 @@ import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
@@ -16,16 +16,16 @@ func updateEnvTests(t *testing.T, s TestSetup) {
t.Helper()
t.Run(`should update envs`, func(t *testing.T) {
req, ctx := createContext(s)
originalAppURL := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL)
originalAppURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL)
data := model.UpdateEnvInput{}
_, err := resolvers.UpdateEnvResolver(ctx, data)
assert.NotNil(t, err)
h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
assert.Nil(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
newURL := "https://test.com"
disableLoginPage := true
allowedOrigins := []string{"http://localhost:8080"}
@@ -37,9 +37,9 @@ func updateEnvTests(t *testing.T, s TestSetup) {
_, err = resolvers.UpdateEnvResolver(ctx, data)
assert.Nil(t, err)
assert.Equal(t, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL), newURL)
assert.True(t, envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableLoginPage))
assert.Equal(t, envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyAllowedOrigins), allowedOrigins)
assert.Equal(t, envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL), newURL)
assert.True(t, envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableLoginPage))
assert.Equal(t, envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyAllowedOrigins), allowedOrigins)
disableLoginPage = false
data = model.UpdateEnvInput{

View File

@@ -1,12 +1,11 @@
package test
import (
"fmt"
"context"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
@@ -34,18 +33,16 @@ func updateProfileTests(t *testing.T, s TestSetup) {
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
assert.NoError(t, err)
token := *verifyRes.AccessToken
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", token))
_, err = resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{
FamilyName: &fName,
})
assert.Nil(t, err)
s.GinContext.Request.Header.Set("Authorization", "Bearer "+*verifyRes.AccessToken)
ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)
newEmail := "new_" + email
_, err = resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{
Email: &newEmail,
})
s.GinContext.Request.Header.Set("Authorization", "")
assert.Nil(t, err)
_, err = resolvers.ProfileResolver(ctx)
assert.NotNil(t, err, "unauthorized")

View File

@@ -5,10 +5,10 @@ import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
@@ -33,9 +33,9 @@ func updateUserTest(t *testing.T, s TestSetup) {
})
assert.NotNil(t, err, "unauthorized")
h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
assert.Nil(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
_, err = resolvers.UpdateUserResolver(ctx, model.UpdateUserInput{
ID: user.ID,
Roles: newRoles,

View File

@@ -2,14 +2,13 @@ package test
import (
"fmt"
"log"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
@@ -36,13 +35,12 @@ func usersTest(t *testing.T, s TestSetup) {
usersRes, err := resolvers.UsersResolver(ctx, pagination)
assert.NotNil(t, err, "unauthorized")
h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
assert.Nil(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
usersRes, err = resolvers.UsersResolver(ctx, pagination)
assert.Nil(t, err)
log.Println("=> userRes:", usersRes)
rLen := len(usersRes.Users)
assert.GreaterOrEqual(t, rLen, 1)

View File

@@ -22,7 +22,7 @@ func TestIsValidEmail(t *testing.T) {
func TestIsValidOrigin(t *testing.T) {
// don't use portocal(http/https) for ALLOWED_ORIGINS while testing,
// as we trim them off while running the main function
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyAllowedOrigins, []string{"localhost:8080", "*.google.com", "*.google.in", "*abc.*"})
envstore.EnvStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyAllowedOrigins, []string{"localhost:8080", "*.google.com", "*.google.in", "*abc.*"})
assert.False(t, utils.IsValidOrigin("http://myapp.com"), "it should be invalid origin")
assert.False(t, utils.IsValidOrigin("http://appgoogle.com"), "it should be invalid origin")
assert.True(t, utils.IsValidOrigin("http://app.google.com"), "it should be valid origin")
@@ -32,7 +32,7 @@ func TestIsValidOrigin(t *testing.T) {
assert.True(t, utils.IsValidOrigin("http://xyx.abc.in"), "it should be valid origin")
assert.True(t, utils.IsValidOrigin("http://xyxabc.in"), "it should be valid origin")
assert.True(t, utils.IsValidOrigin("http://localhost:8080"), "it should be valid origin")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyAllowedOrigins, []string{"*"})
envstore.EnvStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyAllowedOrigins, []string{"*"})
}
func TestIsValidIdentifier(t *testing.T) {

View File

@@ -5,10 +5,10 @@ import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
@@ -19,12 +19,15 @@ func verificationRequestsTest(t *testing.T, s TestSetup) {
req, ctx := createContext(s)
email := "verification_requests." + s.TestInfo.Email
resolvers.SignupResolver(ctx, model.SignUpInput{
res, err := resolvers.SignupResolver(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
assert.NoError(t, err)
assert.NotNil(t, res)
limit := int64(10)
page := int64(1)
pagination := &model.PaginatedInput{
@@ -37,9 +40,9 @@ func verificationRequestsTest(t *testing.T, s TestSetup) {
requests, err := resolvers.VerificationRequestsResolver(ctx, pagination)
assert.NotNil(t, err, "unauthorized")
h, err := utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
h, err := crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
assert.Nil(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), h))
requests, err = resolvers.VerificationRequestsResolver(ctx, pagination)
assert.Nil(t, err)

View File

@@ -5,15 +5,15 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
)
// CreateAdminAuthToken creates the admin token based on secret key
func CreateAdminAuthToken(tokenType string, c *gin.Context) (string, error) {
return utils.EncryptPassword(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
return crypto.EncryptPassword(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret))
}
// GetAdminAuthToken helps in getting the admin token from the request cookie
@@ -23,7 +23,7 @@ func GetAdminAuthToken(gc *gin.Context) (string, error) {
return "", fmt.Errorf("unauthorized")
}
err = bcrypt.CompareHashAndPassword([]byte(token), []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)))
err = bcrypt.CompareHashAndPassword([]byte(token), []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)))
if err != nil {
return "", fmt.Errorf(`unauthorized`)
@@ -41,7 +41,7 @@ func IsSuperAdmin(gc *gin.Context) bool {
return false
}
return secret == envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
return secret == envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
}
return token != ""

View File

@@ -2,23 +2,23 @@ package token
import (
"encoding/json"
"errors"
"fmt"
"log"
"os"
"strings"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
"github.com/google/uuid"
"github.com/robertkrimen/otto"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/utils"
)
// JWTToken is a struct to hold JWT token and its expiration time
@@ -33,57 +33,219 @@ type Token struct {
FingerPrintHash string `json:"fingerprint_hash"`
RefreshToken *JWTToken `json:"refresh_token"`
AccessToken *JWTToken `json:"access_token"`
IDToken *JWTToken `json:"id_token"`
}
// SessionData
type SessionData struct {
Subject string `json:"sub"`
Roles []string `json:"roles"`
Scope []string `json:"scope"`
Nonce string `json:"nonce"`
IssuedAt int64 `json:"iat"`
ExpiresAt int64 `json:"exp"`
}
// CreateSessionToken creates a new session token
func CreateSessionToken(user models.User, nonce string, roles, scope []string) (*SessionData, string, error) {
fingerPrintMap := &SessionData{
Nonce: nonce,
Roles: roles,
Subject: user.ID,
Scope: scope,
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().AddDate(1, 0, 0).Unix(),
}
fingerPrintBytes, _ := json.Marshal(fingerPrintMap)
fingerPrintHash, err := crypto.EncryptAES(string(fingerPrintBytes))
if err != nil {
return nil, "", err
}
return fingerPrintMap, fingerPrintHash, nil
}
// CreateAuthToken creates a new auth token when userlogs in
func CreateAuthToken(user models.User, roles []string) (*Token, error) {
fingerprint := uuid.NewString()
fingerPrintHashBytes, err := utils.EncryptAES([]byte(fingerprint))
func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string) (*Token, error) {
hostname := utils.GetHost(gc)
nonce := uuid.New().String()
_, fingerPrintHash, err := CreateSessionToken(user, nonce, roles, scope)
if err != nil {
return nil, err
}
refreshToken, refreshTokenExpiresAt, err := CreateRefreshToken(user, roles)
accessToken, accessTokenExpiresAt, err := CreateAccessToken(user, roles, scope, hostname, nonce)
if err != nil {
return nil, err
}
accessToken, accessTokenExpiresAt, err := CreateAccessToken(user, roles)
idToken, idTokenExpiresAt, err := CreateIDToken(user, roles, hostname, nonce)
if err != nil {
return nil, err
}
return &Token{
FingerPrint: fingerprint,
FingerPrintHash: string(fingerPrintHashBytes),
RefreshToken: &JWTToken{Token: refreshToken, ExpiresAt: refreshTokenExpiresAt},
res := &Token{
FingerPrint: nonce,
FingerPrintHash: fingerPrintHash,
AccessToken: &JWTToken{Token: accessToken, ExpiresAt: accessTokenExpiresAt},
}, nil
IDToken: &JWTToken{Token: idToken, ExpiresAt: idTokenExpiresAt},
}
if utils.StringSliceContains(scope, "offline_access") {
refreshToken, refreshTokenExpiresAt, err := CreateRefreshToken(user, roles, hostname, nonce)
if err != nil {
return nil, err
}
res.RefreshToken = &JWTToken{Token: refreshToken, ExpiresAt: refreshTokenExpiresAt}
}
return res, nil
}
// CreateRefreshToken util to create JWT token
func CreateRefreshToken(user models.User, roles []string) (string, int64, error) {
func CreateRefreshToken(user models.User, roles []string, hostname, nonce string) (string, int64, error) {
// expires in 1 year
expiryBound := time.Hour * 8760
expiresAt := time.Now().Add(expiryBound).Unix()
customClaims := jwt.MapClaims{
"iss": hostname,
"aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID),
"sub": user.ID,
"exp": expiresAt,
"iat": time.Now().Unix(),
"token_type": constants.TokenTypeRefreshToken,
"roles": roles,
"id": user.ID,
"nonce": nonce,
}
token, err := SignJWTToken(customClaims)
if err != nil {
return "", 0, err
}
return token, expiresAt, nil
}
// CreateAccessToken util to create JWT token, based on
// user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT
func CreateAccessToken(user models.User, roles []string) (string, int64, error) {
func CreateAccessToken(user models.User, roles, scopes []string, hostName, nonce string) (string, int64, error) {
expiryBound := time.Minute * 30
expiresAt := time.Now().Add(expiryBound).Unix()
customClaims := jwt.MapClaims{
"iss": hostName,
"aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID),
"nonce": nonce,
"sub": user.ID,
"exp": expiresAt,
"iat": time.Now().Unix(),
"token_type": constants.TokenTypeAccessToken,
"scope": scopes,
"roles": roles,
}
token, err := SignJWTToken(customClaims)
if err != nil {
return "", 0, err
}
return token, expiresAt, nil
}
// GetAccessToken returns the access token from the request (either from header or cookie)
func GetAccessToken(gc *gin.Context) (string, error) {
// try to check in auth header for cookie
auth := gc.Request.Header.Get("Authorization")
if auth == "" {
return "", fmt.Errorf(`unauthorized`)
}
if !strings.HasPrefix(auth, "Bearer ") {
return "", fmt.Errorf(`not a bearer token`)
}
token := strings.TrimPrefix(auth, "Bearer ")
return token, nil
}
// Function to validate access token for authorizer apis (profile, update_profile)
func ValidateAccessToken(gc *gin.Context, accessToken string) (map[string]interface{}, error) {
var res map[string]interface{}
if accessToken == "" {
return res, fmt.Errorf(`unauthorized`)
}
savedSession := sessionstore.GetState(accessToken)
if savedSession == "" {
return res, fmt.Errorf(`unauthorized`)
}
savedSessionSplit := strings.Split(savedSession, "@")
nonce := savedSessionSplit[0]
userID := savedSessionSplit[1]
hostname := utils.GetHost(gc)
res, err := ParseJWTToken(accessToken, hostname, nonce, userID)
if err != nil {
return res, err
}
if res["token_type"] != constants.TokenTypeAccessToken {
return res, fmt.Errorf(`unauthorized: invalid token type`)
}
return res, nil
}
func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionData, error) {
if encryptedSession == "" {
return nil, fmt.Errorf(`unauthorized`)
}
savedSession := sessionstore.GetState(encryptedSession)
if savedSession == "" {
return nil, fmt.Errorf(`unauthorized`)
}
savedSessionSplit := strings.Split(savedSession, "@")
nonce := savedSessionSplit[0]
userID := savedSessionSplit[1]
decryptedFingerPrint, err := crypto.DecryptAES(encryptedSession)
if err != nil {
return nil, err
}
var res SessionData
err = json.Unmarshal([]byte(decryptedFingerPrint), &res)
if err != nil {
return nil, err
}
if res.Nonce != nonce {
return nil, fmt.Errorf(`unauthorized: invalid nonce`)
}
if res.Subject != userID {
return nil, fmt.Errorf(`unauthorized: invalid user id`)
}
if res.ExpiresAt < time.Now().Unix() {
return nil, fmt.Errorf(`unauthorized: token expired`)
}
// TODO validate scope
// if !reflect.DeepEqual(res.Roles, roles) {
// return res, "", fmt.Errorf(`unauthorized`)
// }
return &res, nil
}
// CreateIDToken util to create JWT token, based on
// user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT
func CreateIDToken(user models.User, roles []string, hostname, nonce string) (string, int64, error) {
expiryBound := time.Minute * 30
expiresAt := time.Now().Add(expiryBound).Unix()
@@ -92,11 +254,15 @@ func CreateAccessToken(user models.User, roles []string) (string, int64, error)
var userMap map[string]interface{}
json.Unmarshal(userBytes, &userMap)
claimKey := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim)
claimKey := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim)
customClaims := jwt.MapClaims{
"iss": hostname,
"aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID),
"nonce": nonce,
"sub": user.ID,
"exp": expiresAt,
"iat": time.Now().Unix(),
"token_type": constants.TokenTypeAccessToken,
"token_type": constants.TokenTypeIdentityToken,
"allowed_roles": strings.Split(user.Roles, ","),
claimKey: roles,
}
@@ -145,58 +311,18 @@ func CreateAccessToken(user models.User, roles []string) (string, int64, error)
return token, expiresAt, nil
}
// GetAccessToken returns the access token from the request (either from header or cookie)
func GetAccessToken(gc *gin.Context) (string, error) {
token, err := cookie.GetAccessTokenCookie(gc)
if err != nil || token == "" {
// try to check in auth header for cookie
auth := gc.Request.Header.Get("Authorization")
if auth == "" {
return "", fmt.Errorf(`unauthorized`)
}
token = strings.TrimPrefix(auth, "Bearer ")
}
return token, nil
}
// GetRefreshToken returns the refresh token from cookie / request query url
func GetRefreshToken(gc *gin.Context) (string, error) {
token, err := cookie.GetRefreshTokenCookie(gc)
if err != nil || token == "" {
// GetIDToken returns the id token from the request header
func GetIDToken(gc *gin.Context) (string, error) {
// try to check in auth header for cookie
auth := gc.Request.Header.Get("Authorization")
if auth == "" {
return "", fmt.Errorf(`unauthorized`)
}
if !strings.HasPrefix(auth, "Bearer ") {
return "", fmt.Errorf(`not a bearer token`)
}
token := strings.TrimPrefix(auth, "Bearer ")
return token, nil
}
// GetFingerPrint returns the finger print from cookie
func GetFingerPrint(gc *gin.Context) (string, error) {
fingerPrint, err := cookie.GetFingerPrintCookie(gc)
if err != nil || fingerPrint == "" {
return "", fmt.Errorf(`no finger print`)
}
return fingerPrint, nil
}
func ValidateAccessToken(gc *gin.Context) (map[string]interface{}, error) {
token, err := GetAccessToken(gc)
if err != nil {
return nil, err
}
claims, err := ParseJWTToken(token)
if err != nil {
return nil, err
}
// also validate if there is user session present with access token
sessions := sessionstore.GetUserSessions(claims["id"].(string))
if len(sessions) == 0 {
return nil, errors.New("unauthorized")
}
return claims, nil
}

View File

@@ -4,13 +4,14 @@ import (
"errors"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/golang-jwt/jwt"
)
// SignJWTToken common util to sing jwt token
func SignJWTToken(claims jwt.MapClaims) (string, error) {
jwtType := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
jwtType := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
signingMethod := jwt.GetSigningMethod(jwtType)
if signingMethod == nil {
return "", errors.New("unsupported signing method")
@@ -23,18 +24,19 @@ func SignJWTToken(claims jwt.MapClaims) (string, error) {
switch signingMethod {
case jwt.SigningMethodHS256, jwt.SigningMethodHS384, jwt.SigningMethodHS512:
return t.SignedString([]byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret)))
return t.SignedString([]byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret)))
case jwt.SigningMethodRS256, jwt.SigningMethodRS384, jwt.SigningMethodRS512:
key, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPrivateKey)))
key, err := crypto.ParseRsaPrivateKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPrivateKey))
if err != nil {
return "", err
}
return t.SignedString(key)
case jwt.SigningMethodES256, jwt.SigningMethodES384, jwt.SigningMethodES512:
key, err := jwt.ParseECPrivateKeyFromPEM([]byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPrivateKey)))
key, err := crypto.ParseEcdsaPrivateKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPrivateKey))
if err != nil {
return "", err
}
return t.SignedString(key)
default:
return "", errors.New("unsupported signing method")
@@ -42,8 +44,8 @@ func SignJWTToken(claims jwt.MapClaims) (string, error) {
}
// ParseJWTToken common util to parse jwt token
func ParseJWTToken(token string) (jwt.MapClaims, error) {
jwtType := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
func ParseJWTToken(token, hostname, nonce, subject string) (jwt.MapClaims, error) {
jwtType := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
signingMethod := jwt.GetSigningMethod(jwtType)
var err error
@@ -52,11 +54,11 @@ func ParseJWTToken(token string) (jwt.MapClaims, error) {
switch signingMethod {
case jwt.SigningMethodHS256, jwt.SigningMethodHS384, jwt.SigningMethodHS512:
_, err = jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) {
return []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret)), nil
return []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret)), nil
})
case jwt.SigningMethodRS256, jwt.SigningMethodRS384, jwt.SigningMethodRS512:
_, err = jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) {
key, err := jwt.ParseRSAPublicKeyFromPEM([]byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)))
key, err := crypto.ParseRsaPublicKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey))
if err != nil {
return nil, err
}
@@ -64,7 +66,7 @@ func ParseJWTToken(token string) (jwt.MapClaims, error) {
})
case jwt.SigningMethodES256, jwt.SigningMethodES384, jwt.SigningMethodES512:
_, err = jwt.ParseWithClaims(token, &claims, func(token *jwt.Token) (interface{}, error) {
key, err := jwt.ParseECPublicKeyFromPEM([]byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey)))
key, err := crypto.ParseEcdsaPublicKeyFromPemStr(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtPublicKey))
if err != nil {
return nil, err
}
@@ -85,5 +87,21 @@ func ParseJWTToken(token string) (jwt.MapClaims, error) {
claims["exp"] = intExp
claims["iat"] = intIat
if claims["aud"] != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) {
return claims, errors.New("invalid audience")
}
if claims["nonce"] != nonce {
return claims, errors.New("invalid nonce")
}
if claims["iss"] != hostname {
return claims, errors.New("invalid issuer")
}
if claims["sub"] != subject {
return claims, errors.New("invalid subject")
}
return claims, nil
}

View File

@@ -9,14 +9,16 @@ import (
)
// CreateVerificationToken creates a verification JWT token
func CreateVerificationToken(email, tokenType, hostname string) (string, error) {
func CreateVerificationToken(email, tokenType, hostname, nonceHash string) (string, error) {
claims := jwt.MapClaims{
"iss": hostname,
"aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID),
"sub": email,
"exp": time.Now().Add(time.Minute * 30).Unix(),
"iat": time.Now().Unix(),
"token_type": tokenType,
"email": email,
"host": hostname,
"redirect_url": envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL),
"nonce": nonceHash,
"redirect_url": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL),
}
return SignJWTToken(claims)

Some files were not shown because too many files have changed in this diff Show More