├── dashboard
├── public
│ ├── sample.csv
│ └── roblox.png
├── favicon_io
│ ├── favicon.ico
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── apple-touch-icon.png
│ ├── android-chrome-192x192.png
│ └── android-chrome-512x512.png
├── .prettierrc.json
├── README.md
├── src
│ ├── index.tsx
│ ├── pages
│ │ └── Home.tsx
│ ├── layouts
│ │ ├── DashboardLayout.tsx
│ │ └── AuthLayout.tsx
│ ├── components
│ │ └── EnvComponents
│ │ │ ├── DomainWhitelisting.tsx
│ │ │ ├── SessionStorage.tsx
│ │ │ └── OrganizationInfo.tsx
│ ├── utils
│ │ └── parseCSV.ts
│ ├── App.tsx
│ ├── contexts
│ │ └── AuthContext.tsx
│ └── routes
│ │ └── index.tsx
├── esbuild.config.js
└── package.json
├── .github
├── CODE_OF_CONDUCT.md
├── FUNDING.yml
├── PULL_REQUEST_TEMPLATE.md
└── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
├── assets
├── logo.png
└── authorizer-architecture.png
├── app
├── favicon_io
│ ├── favicon.ico
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── apple-touch-icon.png
│ ├── android-chrome-192x192.png
│ └── android-chrome-512x512.png
├── .prettierrc.json
├── src
│ ├── index.tsx
│ ├── pages
│ │ ├── rest-password.tsx
│ │ ├── setup-password.tsx
│ │ ├── signup.tsx
│ │ └── dashboard.tsx
│ ├── theme.ts
│ ├── index.css
│ ├── utils
│ │ └── common.ts
│ └── App.tsx
├── README.md
├── esbuild.config.js
└── package.json
├── docker-compose.yaml
├── server
├── constants
│ ├── pagination.go
│ ├── authenticator_method.go
│ ├── cookie.go
│ ├── token_types.go
│ ├── webhook_event.go
│ ├── oauth2.go
│ ├── verification_types.go
│ ├── db_types.go
│ ├── auth_methods.go
│ └── oauth_info_urls.go
├── tools.go
├── graph
│ └── resolver.go
├── validators
│ ├── email.go
│ ├── common.go
│ ├── roles.go
│ ├── verification_requests.go
│ ├── email_template.go
│ ├── webhook.go
│ ├── url.go
│ └── password.go
├── refs
│ ├── bool.go
│ ├── int.go
│ └── string.go
├── handlers
│ ├── root.go
│ ├── health.go
│ ├── graphql.go
│ ├── dashboard.go
│ ├── jwks.go
│ ├── playground.go
│ ├── openid_config.go
│ ├── userinfo.go
│ └── logout.go
├── memorystore
│ └── providers
│ │ ├── inmemory
│ │ ├── provider_test.go
│ │ ├── provider.go
│ │ └── stores
│ │ │ ├── state_store.go
│ │ │ └── env_store.go
│ │ ├── redis
│ │ └── provider_test.go
│ │ └── providers.go
├── middlewares
│ ├── context.go
│ ├── client_check.go
│ └── cors.go
├── db
│ ├── providers
│ │ ├── provider_template
│ │ │ ├── provider.go
│ │ │ ├── session.go
│ │ │ ├── otp.go
│ │ │ ├── webhook_log.go
│ │ │ ├── env.go
│ │ │ ├── authenticator.go
│ │ │ ├── email_template.go
│ │ │ ├── verification_requests.go
│ │ │ └── webhook.go
│ │ ├── dynamodb
│ │ │ ├── session.go
│ │ │ ├── shared.go
│ │ │ ├── env.go
│ │ │ └── authenticator.go
│ │ ├── arangodb
│ │ │ ├── session.go
│ │ │ └── env.go
│ │ ├── sql
│ │ │ ├── session.go
│ │ │ ├── env.go
│ │ │ └── authenticator.go
│ │ ├── couchbase
│ │ │ ├── session.go
│ │ │ ├── shared.go
│ │ │ └── env.go
│ │ ├── mongodb
│ │ │ ├── session.go
│ │ │ ├── env.go
│ │ │ └── authenticator.go
│ │ └── cassandradb
│ │ │ ├── session.go
│ │ │ └── env.go
│ └── models
│ │ ├── session.go
│ │ ├── env.go
│ │ ├── authenticators.go
│ │ ├── otp.go
│ │ ├── model.go
│ │ ├── email_templates.go
│ │ └── webhook_log.go
├── types
│ └── interface_slice.go
├── authenticators
│ ├── providers
│ │ ├── totp
│ │ │ └── provider.go
│ │ └── providers.go
│ └── totp_store.go
├── crypto
│ ├── b64.go
│ └── hmac.go
├── cli
│ └── cli.go
├── utils
│ ├── parser.go
│ ├── response.go
│ ├── generate_totp_recovery_code.go
│ ├── request_info.go
│ ├── gin_context.go
│ ├── generate_otp.go
│ ├── pagination.go
│ ├── pkce.go
│ ├── nonce.go
│ └── file.go
├── resolvers
│ ├── revoke.go
│ ├── admin_logout.go
│ ├── webhook.go
│ ├── users.go
│ ├── webhooks.go
│ ├── profile.go
│ ├── email_templates.go
│ ├── verification_requests.go
│ ├── logout.go
│ ├── delete_webhook.go
│ ├── admin_login.go
│ ├── webhook_logs.go
│ ├── delete_email_template.go
│ ├── admin_session.go
│ ├── user.go
│ ├── enable_access.go
│ ├── deactivate_account.go
│ ├── revoke_access.go
│ └── add_email_template.go
├── test
│ ├── urls_test.go
│ ├── meta_test.go
│ ├── cors_test.go
│ ├── admin_login_test.go
│ ├── admin_signup_test.go
│ ├── admin_logout_test.go
│ ├── admin_session_test.go
│ ├── env_test.go
│ ├── email_templates_test.go
│ ├── resend_verify_email_test.go
│ ├── webhooks_test.go
│ ├── env_file_test.go
│ ├── test_endpoint_test.go
│ ├── forgot_password_test.go
│ ├── delete_user_test.go
│ ├── delete_email_template_test.go
│ ├── verify_email_test.go
│ ├── users_test.go
│ ├── webhook_test.go
│ ├── deactivate_account_test.go
│ ├── delete_webhook_test.go
│ ├── profile_test.go
│ ├── magic_link_login_test.go
│ ├── verification_requests_test.go
│ ├── invite_member_test.go
│ ├── webhook_logs_test.go
│ ├── update_profile_test.go
│ └── add_webhook_test.go
├── token
│ ├── verification_token.go
│ └── admin_token.go
├── logs
│ └── logs.go
├── smsproviders
│ └── twilio.go
└── cookie
│ └── admin_cookie.go
├── .dockerignore
├── .env.sample
├── .gitignore
├── templates
├── authorize_web_message.tmpl
├── authorize_form_post.tmpl
└── app.tmpl
├── .env.test
├── scripts
├── build-mac.sh
└── couchbase-test.sh
├── LICENSE
└── Dockerfile
/dashboard/public/sample.csv:
--------------------------------------------------------------------------------
1 | foo@bar.com,test@authorizer.dev
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
--------------------------------------------------------------------------------
/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/localnerve/authorizer/public-package/assets/logo.png
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: authorizerdev
4 |
--------------------------------------------------------------------------------
/app/favicon_io/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/localnerve/authorizer/public-package/app/favicon_io/favicon.ico
--------------------------------------------------------------------------------
/dashboard/public/roblox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/localnerve/authorizer/public-package/dashboard/public/roblox.png
--------------------------------------------------------------------------------
/app/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "singleQuote": true,
4 | "trailingComma": "all",
5 | "useTabs": true
6 | }
7 |
--------------------------------------------------------------------------------
/app/favicon_io/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/localnerve/authorizer/public-package/app/favicon_io/favicon-16x16.png
--------------------------------------------------------------------------------
/app/favicon_io/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/localnerve/authorizer/public-package/app/favicon_io/favicon-32x32.png
--------------------------------------------------------------------------------
/dashboard/favicon_io/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/localnerve/authorizer/public-package/dashboard/favicon_io/favicon.ico
--------------------------------------------------------------------------------
/app/favicon_io/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/localnerve/authorizer/public-package/app/favicon_io/apple-touch-icon.png
--------------------------------------------------------------------------------
/assets/authorizer-architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/localnerve/authorizer/public-package/assets/authorizer-architecture.png
--------------------------------------------------------------------------------
/dashboard/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "singleQuote": true,
4 | "trailingComma": "all",
5 | "useTabs": true
6 | }
7 |
--------------------------------------------------------------------------------
/dashboard/favicon_io/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/localnerve/authorizer/public-package/dashboard/favicon_io/favicon-16x16.png
--------------------------------------------------------------------------------
/dashboard/favicon_io/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/localnerve/authorizer/public-package/dashboard/favicon_io/favicon-32x32.png
--------------------------------------------------------------------------------
/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | authorizer:
3 | build: .
4 | ports:
5 | - 8080:8080
6 | env_file:
7 | - .env
8 |
--------------------------------------------------------------------------------
/server/constants/pagination.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | // DefaultLimit is the default limit for pagination
4 | var DefaultLimit = 10
5 |
--------------------------------------------------------------------------------
/server/tools.go:
--------------------------------------------------------------------------------
1 | //go:build tools
2 | // +build tools
3 |
4 | package tools
5 |
6 | import (
7 | _ "github.com/99designs/gqlgen"
8 | )
9 |
--------------------------------------------------------------------------------
/app/favicon_io/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/localnerve/authorizer/public-package/app/favicon_io/android-chrome-192x192.png
--------------------------------------------------------------------------------
/app/favicon_io/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/localnerve/authorizer/public-package/app/favicon_io/android-chrome-512x512.png
--------------------------------------------------------------------------------
/dashboard/favicon_io/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/localnerve/authorizer/public-package/dashboard/favicon_io/apple-touch-icon.png
--------------------------------------------------------------------------------
/dashboard/favicon_io/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/localnerve/authorizer/public-package/dashboard/favicon_io/android-chrome-192x192.png
--------------------------------------------------------------------------------
/dashboard/favicon_io/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/localnerve/authorizer/public-package/dashboard/favicon_io/android-chrome-512x512.png
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | server/.env
2 | server/server
3 | .git
4 | .gitignore
5 | README.md
6 | ROADMAP.md
7 | build
8 | .env
9 | data.db
10 | app/node_modules
11 | app/build
12 | certs/
13 |
--------------------------------------------------------------------------------
/app/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import './index.css';
5 |
6 | ReactDOM.render(, document.getElementById('root'));
7 |
--------------------------------------------------------------------------------
/server/constants/authenticator_method.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | // Authenticators Methods
4 | const (
5 | // EnvKeyTOTPAuthenticator key for env variable TOTP
6 | EnvKeyTOTPAuthenticator = "totp"
7 | )
8 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | #### What does this PR do?
2 |
3 | #### Which issue(s) does this PR fix?
4 |
5 | #### If this PR affects any API reference documentation, please share the updated endpoint references
6 |
--------------------------------------------------------------------------------
/dashboard/README.md:
--------------------------------------------------------------------------------
1 | # Authorizer dashboard
2 |
3 | ### Getting started
4 |
5 | **Setting up locally**
6 |
7 | - `cd dashboard`
8 | - `npm start`
9 |
10 | **Creating production build**
11 |
12 | - `make build-dashboard`
13 |
--------------------------------------------------------------------------------
/server/graph/resolver.go:
--------------------------------------------------------------------------------
1 | package graph
2 |
3 | // This file will not be regenerated automatically.
4 | //
5 | // It serves as dependency injection for your app, add any dependencies you require here.
6 |
7 | type Resolver struct{}
8 |
--------------------------------------------------------------------------------
/dashboard/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render(
6 |
,
9 | document.getElementById('root'),
10 | );
11 |
--------------------------------------------------------------------------------
/server/validators/email.go:
--------------------------------------------------------------------------------
1 | package validators
2 |
3 | import "net/mail"
4 |
5 | // IsValidEmail validates email
6 | func IsValidEmail(email string) bool {
7 | _, err := mail.ParseAddress(email)
8 | return err == nil
9 | }
10 |
--------------------------------------------------------------------------------
/.env.sample:
--------------------------------------------------------------------------------
1 | ENV=production
2 | DATABASE_URL=data.db
3 | DATABASE_TYPE=sqlite
4 | CUSTOM_ACCESS_TOKEN_SCRIPT="function(user,tokenPayload){var data = tokenPayload;data.extra = {'x-extra-id': user.id};return data;}"
5 | DISABLE_PLAYGROUND=true
6 |
--------------------------------------------------------------------------------
/app/README.md:
--------------------------------------------------------------------------------
1 | # Authorizer APP
2 |
3 | App that can be used as login wall for your any application in combination with @authorizerdev/@authorizer.js
4 |
5 | ### Getting started
6 |
7 | **Setting up locally**
8 |
9 | - `cd app`
10 | - `npm start`
11 |
12 | **Creating production build**
13 |
14 | - `make build-app`
15 |
--------------------------------------------------------------------------------
/server/refs/bool.go:
--------------------------------------------------------------------------------
1 | package refs
2 |
3 | // NewBoolRef returns a reference to a bool with given value
4 | func NewBoolRef(v bool) *bool {
5 | return &v
6 | }
7 |
8 | // BoolValue returns the value of the given bool ref
9 | func BoolValue(r *bool) bool {
10 | if r == nil {
11 | return false
12 | }
13 | return *r
14 | }
--------------------------------------------------------------------------------
/app/esbuild.config.js:
--------------------------------------------------------------------------------
1 | const __is_prod__ = process.env.NODE_ENV === 'production';
2 | require('esbuild').build({
3 | entryPoints: ['src/index.tsx'],
4 | chunkNames: '[name]-[hash]',
5 | bundle: true,
6 | minify: __is_prod__,
7 | outdir: 'build',
8 | splitting: true,
9 | format: 'esm',
10 | watch: !__is_prod__,
11 | });
12 |
--------------------------------------------------------------------------------
/server/handlers/root.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | // RootHandler is the handler for / root route.
10 | func RootHandler() gin.HandlerFunc {
11 | return func(c *gin.Context) {
12 | c.Redirect(http.StatusTemporaryRedirect, "/dashboard")
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/server/refs/int.go:
--------------------------------------------------------------------------------
1 | package refs
2 |
3 | // NewInt64Ref returns a reference to a int64 with given value
4 | func NewInt64Ref(v int64) *int64 {
5 | return &v
6 | }
7 |
8 | // Int64Value returns the value of the given bool ref
9 | func Int64Value(r *int64) int64 {
10 | if r == nil {
11 | return 0
12 | }
13 | return *r
14 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | server/server
2 | server/.env
3 | data
4 | app/node_modules
5 | app/build
6 | dashboard/node_modules
7 | dashboard/build
8 | build
9 | .env
10 | data.db
11 | test.db
12 | .DS_Store
13 | .env.local
14 | *.tar.gz
15 | .vscode/
16 | .yalc
17 | yalc.lock
18 | certs/
19 | *-shm
20 | *-wal
21 | .idea
22 | *.iml
23 | *.code-workspace
24 | tmp/
--------------------------------------------------------------------------------
/dashboard/esbuild.config.js:
--------------------------------------------------------------------------------
1 | const __is_prod__ = process.env.NODE_ENV === 'production';
2 | require('esbuild').build({
3 | entryPoints: ['src/index.tsx'],
4 | chunkNames: '[name]-[hash]',
5 | bundle: true,
6 | minify: __is_prod__,
7 | outdir: 'build',
8 | splitting: true,
9 | format: 'esm',
10 | watch: !__is_prod__,
11 | logLevel: 'info',
12 | });
13 |
--------------------------------------------------------------------------------
/server/handlers/health.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | // HealthHandler is the handler for /health route.
10 | // It states if server is in healthy state or not
11 | func HealthHandler() gin.HandlerFunc {
12 | return func(c *gin.Context) {
13 | c.String(http.StatusOK, "OK")
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/server/validators/common.go:
--------------------------------------------------------------------------------
1 | package validators
2 |
3 | // IsStringArrayEqual validates if string array are equal.
4 | // This does check if the order is same
5 | func IsStringArrayEqual(a, b []string) bool {
6 | if len(a) != len(b) {
7 | return false
8 | }
9 | for i, v := range a {
10 | if v != b[i] {
11 | return false
12 | }
13 | }
14 | return true
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/pages/rest-password.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { AuthorizerResetPassword } from '@authorizerdev/authorizer-react';
3 |
4 | export default function ResetPassword() {
5 | return (
6 |
7 | Reset Password
8 |
9 |
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/pages/setup-password.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { AuthorizerResetPassword } from '@authorizerdev/authorizer-react';
3 |
4 | export default function SetupPassword() {
5 | return (
6 |
7 | Setup new Password
8 |
9 |
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/server/memorystore/providers/inmemory/provider_test.go:
--------------------------------------------------------------------------------
1 | package inmemory
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/authorizerdev/authorizer/server/memorystore/providers"
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestInMemoryProvider(t *testing.T) {
11 | p, err := NewInMemoryProvider()
12 | assert.NoError(t, err)
13 | providers.ProviderTests(t, p)
14 | }
15 |
--------------------------------------------------------------------------------
/server/memorystore/providers/redis/provider_test.go:
--------------------------------------------------------------------------------
1 | package redis
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/assert"
7 |
8 | "github.com/authorizerdev/authorizer/server/memorystore/providers"
9 | )
10 |
11 | func TestRedisProvider(t *testing.T) {
12 | p, err := NewRedisProvider("redis://127.0.0.1:6379")
13 | assert.NoError(t, err)
14 | providers.ProviderTests(t, p)
15 | }
16 |
--------------------------------------------------------------------------------
/server/validators/roles.go:
--------------------------------------------------------------------------------
1 | package validators
2 |
3 | import "github.com/authorizerdev/authorizer/server/utils"
4 |
5 | // IsValidRoles validates roles
6 | func IsValidRoles(userRoles []string, roles []string) bool {
7 | valid := true
8 | for _, userRole := range userRoles {
9 | if !utils.StringSliceContains(roles, userRole) {
10 | valid = false
11 | break
12 | }
13 | }
14 |
15 | return valid
16 | }
17 |
--------------------------------------------------------------------------------
/server/constants/cookie.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | const (
4 | // AppCookieName is the name of the cookie that is used to store the application token
5 | AppCookieName = "cookie"
6 | // AdminCookieName is the name of the cookie that is used to store the admin token
7 | AdminCookieName = "authorizer-admin"
8 | // MfaCookieName is the name of the cookie that is used to store the mfa session
9 | MfaCookieName = "mfa"
10 | )
11 |
--------------------------------------------------------------------------------
/server/refs/string.go:
--------------------------------------------------------------------------------
1 | package refs
2 |
3 | // NewStringRef returns a reference to a string with given value
4 | func NewStringRef(v string) *string {
5 | return &v
6 | }
7 |
8 | // StringValue returns the value of the given string ref
9 | func StringValue(r *string, defaultValue ...string) string {
10 | if r != nil {
11 | return *r
12 | }
13 | if len(defaultValue) > 0 {
14 | return defaultValue[0]
15 | }
16 | return ""
17 | }
18 |
19 |
20 |
--------------------------------------------------------------------------------
/server/middlewares/context.go:
--------------------------------------------------------------------------------
1 | package middlewares
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/gin-gonic/gin"
7 | )
8 |
9 | // GinContextToContextMiddleware is a middleware to add gin context in context
10 | func GinContextToContextMiddleware() gin.HandlerFunc {
11 | return func(c *gin.Context) {
12 | ctx := context.WithValue(c.Request.Context(), "GinContextKey", c)
13 | c.Request = c.Request.WithContext(ctx)
14 | c.Next()
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/server/db/providers/provider_template/provider.go:
--------------------------------------------------------------------------------
1 | package provider_template
2 |
3 | import (
4 | "gorm.io/gorm"
5 | )
6 |
7 | // TODO change following provider to new db provider
8 | type provider struct {
9 | db *gorm.DB
10 | }
11 |
12 | // NewProvider returns a new SQL provider
13 | // TODO change following provider to new db provider
14 | func NewProvider() (*provider, error) {
15 | var sqlDB *gorm.DB
16 |
17 | return &provider{
18 | db: sqlDB,
19 | }, nil
20 | }
21 |
--------------------------------------------------------------------------------
/server/types/interface_slice.go:
--------------------------------------------------------------------------------
1 | package types
2 |
3 | import "encoding/json"
4 |
5 | // Type for interface slice. Used for redis store.
6 | type InterfaceSlice []interface{}
7 |
8 | // MarshalBinary for interface slice.
9 | func (s InterfaceSlice) MarshalBinary() ([]byte, error) {
10 | return json.Marshal(s)
11 | }
12 |
13 | // UnmarshalBinary for interface slice.
14 | func (s *InterfaceSlice) UnmarshalBinary(data []byte) error {
15 | return json.Unmarshal(data, s)
16 | }
17 |
--------------------------------------------------------------------------------
/templates/authorize_web_message.tmpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Authorization Response
5 |
6 |
7 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/server/authenticators/providers/totp/provider.go:
--------------------------------------------------------------------------------
1 | package totp
2 |
3 | import (
4 | "context"
5 | )
6 |
7 | type provider struct {
8 | ctx context.Context
9 | }
10 |
11 | // TOTPConfig defines totp config
12 | type TOTPConfig struct {
13 | ScannerImage string
14 | Secret string
15 | }
16 |
17 | // NewProvider returns a new totp provider
18 | func NewProvider() (*provider, error) {
19 | ctx := context.Background()
20 | return &provider{
21 | ctx: ctx,
22 | }, nil
23 | }
24 |
--------------------------------------------------------------------------------
/server/crypto/b64.go:
--------------------------------------------------------------------------------
1 | package crypto
2 |
3 | import "encoding/base64"
4 |
5 | // EncryptB64 encrypts data into base64 string
6 | func EncryptB64(text string) string {
7 | return base64.StdEncoding.EncodeToString([]byte(text))
8 | }
9 |
10 | // DecryptB64 decrypts from base64 string to readable string
11 | func DecryptB64(s string) (string, error) {
12 | data, err := base64.StdEncoding.DecodeString(s)
13 | if err != nil {
14 | return "", err
15 | }
16 | return string(data), nil
17 | }
18 |
--------------------------------------------------------------------------------
/server/cli/cli.go:
--------------------------------------------------------------------------------
1 | package cli
2 |
3 | var (
4 | // ARG_DB_URL is the cli arg variable for the database url
5 | ARG_DB_URL *string
6 | // ARG_DB_TYPE is the cli arg variable for the database type
7 | ARG_DB_TYPE *string
8 | // ARG_ENV_FILE is the cli arg variable for the env file
9 | ARG_ENV_FILE *string
10 | // ARG_LOG_LEVEL is the cli arg variable for the log level
11 | ARG_LOG_LEVEL *string
12 | // ARG_REDIS_URL is the cli arg variable for the redis url
13 | ARG_REDIS_URL *string
14 | )
15 |
--------------------------------------------------------------------------------
/server/utils/parser.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "errors"
5 | "time"
6 | )
7 |
8 | // ParseDurationInSeconds parses input s, removes ms/us/ns and returns result duration
9 | func ParseDurationInSeconds(s string) (time.Duration, error) {
10 | d, err := time.ParseDuration(s)
11 | if err != nil {
12 | return 0, err
13 | }
14 |
15 | d = d.Truncate(time.Second)
16 | if d <= 0 {
17 | return 0, errors.New(`duration must be greater than 0s`)
18 | }
19 |
20 | return d, nil
21 | }
22 |
--------------------------------------------------------------------------------
/server/constants/token_types.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | const (
4 | // TokenTypeRefreshToken is the refresh_token token type
5 | TokenTypeRefreshToken = "refresh_token"
6 | // TokenTypeAccessToken is the access_token token type
7 | TokenTypeAccessToken = "access_token"
8 | // TokenTypeIdentityToken is the identity_token token type
9 | TokenTypeIdentityToken = "id_token"
10 | // TokenTypeSessionToken is the session_token type used for browser session
11 | TokenTypeSessionToken = "session_token"
12 | )
13 |
--------------------------------------------------------------------------------
/server/utils/response.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "net/http"
5 | "strings"
6 |
7 | "github.com/gin-gonic/gin"
8 | )
9 |
10 | // HandleRedirectORJsonResponse handles the response based on redirectURL
11 | func HandleRedirectORJsonResponse(c *gin.Context, httpResponse int, response map[string]interface{}, redirectURL string) {
12 | if strings.TrimSpace(redirectURL) == "" {
13 | c.JSON(httpResponse, response)
14 | } else {
15 | c.Redirect(http.StatusTemporaryRedirect, redirectURL)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/dashboard/src/pages/Home.tsx:
--------------------------------------------------------------------------------
1 | import { Text } from '@chakra-ui/react';
2 | import React from 'react';
3 |
4 | export default function Home() {
5 | return (
6 | <>
7 |
8 | Hi there 👋
9 |
10 |
11 |
12 | Welcome to Authorizer Administrative Dashboard!
13 | Please use this dashboard to configure your environment variables or
14 | have look at your users
15 |
16 | >
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/server/resolvers/revoke.go:
--------------------------------------------------------------------------------
1 | package resolvers
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/authorizerdev/authorizer/server/graph/model"
7 | "github.com/authorizerdev/authorizer/server/memorystore"
8 | )
9 |
10 | // RevokeResolver resolver to revoke refresh token
11 | func RevokeResolver(ctx context.Context, params model.OAuthRevokeInput) (*model.Response, error) {
12 | memorystore.Provider.RemoveState(params.RefreshToken)
13 | return &model.Response{
14 | Message: "Token revoked",
15 | }, nil
16 | }
17 |
--------------------------------------------------------------------------------
/server/utils/generate_totp_recovery_code.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "math/rand"
5 | "time"
6 | )
7 |
8 | // GenerateTOTPRecoveryCode generates a random 16-character recovery code.
9 | func GenerateTOTPRecoveryCode() string {
10 | const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
11 | code := make([]byte, 16)
12 |
13 | rand.Seed(time.Now().UnixNano())
14 | for i := range code {
15 | code[i] = charset[rand.Intn(len(charset))]
16 | }
17 |
18 | return string(code)
19 | }
20 |
--------------------------------------------------------------------------------
/.env.test:
--------------------------------------------------------------------------------
1 | ENV=test
2 | DATABASE_URL=test.db
3 | DATABASE_TYPE=sqlite
4 | CUSTOM_ACCESS_TOKEN_SCRIPT="function(user,tokenPayload){var data = tokenPayload;data.extra = {'x-extra-id': user.id};return data;}"
5 | SMTP_HOST=smtp.mailtrap.io
6 | SMTP_PORT=2525
7 | SMTP_USERNAME=test
8 | SMTP_PASSWORD=test
9 | SENDER_EMAIL="info@authorizer.dev"
10 | TWILIO_API_KEY=test
11 | TWILIO_API_SECRET=test
12 | TWILIO_ACCOUNT_SID=ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
13 | TWILIO_SENDER=909921212112
14 | SENDER_NAME="Authorizer"
15 | AWS_REGION=ap-south-1
--------------------------------------------------------------------------------
/templates/authorize_form_post.tmpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Authorization Response
5 |
6 |
7 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/server/utils/request_info.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "net/http"
4 |
5 | // GetIP helps in getting the IP address from the request
6 | func GetIP(r *http.Request) string {
7 | IPAddress := r.Header.Get("X-Real-Ip")
8 | if IPAddress == "" {
9 | IPAddress = r.Header.Get("X-Forwarded-For")
10 | }
11 |
12 | if IPAddress == "" {
13 | IPAddress = r.RemoteAddr
14 | }
15 | return IPAddress
16 | }
17 |
18 | // GetUserAgent helps in getting the user agent from the request
19 | func GetUserAgent(r *http.Request) string {
20 | return r.UserAgent()
21 | }
22 |
--------------------------------------------------------------------------------
/server/validators/verification_requests.go:
--------------------------------------------------------------------------------
1 | package validators
2 |
3 | import "github.com/authorizerdev/authorizer/server/constants"
4 |
5 | // IsValidVerificationIdentifier validates verification identifier that is used to identify
6 | // the type of verification request
7 | func IsValidVerificationIdentifier(identifier string) bool {
8 | if identifier != constants.VerificationTypeBasicAuthSignup && identifier != constants.VerificationTypeForgotPassword && identifier != constants.VerificationTypeUpdateEmail {
9 | return false
10 | }
11 | return true
12 | }
13 |
--------------------------------------------------------------------------------
/server/utils/gin_context.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/gin-gonic/gin"
8 | )
9 |
10 | // GinContext to get gin context from context
11 | func GinContextFromContext(ctx context.Context) (*gin.Context, error) {
12 | ginContext := ctx.Value("GinContextKey")
13 | if ginContext == nil {
14 | err := fmt.Errorf("could not retrieve gin.Context")
15 | return nil, err
16 | }
17 |
18 | gc, ok := ginContext.(*gin.Context)
19 | if !ok {
20 | err := fmt.Errorf("gin.Context has wrong type")
21 | return nil, err
22 | }
23 | return gc, nil
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/theme.ts:
--------------------------------------------------------------------------------
1 | // colors: https://tailwindcss.com/docs/customizing-colors
2 |
3 | export const theme = {
4 | colors: {
5 | primary: '#3B82F6',
6 | primaryDisabled: '#60A5FA',
7 | gray: '#D1D5DB',
8 | danger: '#DC2626',
9 | success: '#10B981',
10 | textColor: '#374151',
11 | },
12 | fonts: {
13 | // typography
14 | fontStack: '-apple-system, system-ui, sans-serif',
15 |
16 | // font sizes
17 | largeText: '18px',
18 | mediumText: '14px',
19 | smallText: '12px',
20 | tinyText: '10px',
21 | },
22 |
23 | radius: {
24 | card: '5px',
25 | button: '5px',
26 | input: '5px',
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/server/utils/generate_otp.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "math/rand"
5 | "time"
6 | )
7 |
8 | // GenerateOTP to generate random 6 digit otp
9 | func GenerateOTP() string {
10 | code := ""
11 | codeLength := 6
12 | charSet := "ABCDEFGHJKLMNPQRSTUVWXYZ123456789"
13 | charSetLength := int32(len(charSet))
14 | for i := 0; i < codeLength; i++ {
15 | index := randomNumber(0, charSetLength)
16 | code += string(charSet[index])
17 | }
18 |
19 | return code
20 | }
21 |
22 | func randomNumber(min, max int32) int32 {
23 | rand.Seed(time.Now().UnixNano())
24 | return min + int32(rand.Intn(int(max-min)))
25 | }
26 |
--------------------------------------------------------------------------------
/server/validators/email_template.go:
--------------------------------------------------------------------------------
1 | package validators
2 |
3 | import "github.com/authorizerdev/authorizer/server/constants"
4 |
5 | // IsValidEmailTemplateEventName function to validate email template events
6 | func IsValidEmailTemplateEventName(eventName string) bool {
7 | if eventName != constants.VerificationTypeBasicAuthSignup && eventName != constants.VerificationTypeForgotPassword && eventName != constants.VerificationTypeMagicLinkLogin && eventName != constants.VerificationTypeUpdateEmail && eventName != constants.VerificationTypeOTP && eventName != constants.VerificationTypeInviteMember {
8 | return false
9 | }
10 |
11 | return true
12 | }
13 |
--------------------------------------------------------------------------------
/server/validators/webhook.go:
--------------------------------------------------------------------------------
1 | package validators
2 |
3 | import "github.com/authorizerdev/authorizer/server/constants"
4 |
5 | // IsValidWebhookEventName to validate webhook event name
6 | func IsValidWebhookEventName(eventName string) bool {
7 | if eventName != constants.UserCreatedWebhookEvent && eventName != constants.UserLoginWebhookEvent && eventName != constants.UserSignUpWebhookEvent && eventName != constants.UserDeletedWebhookEvent && eventName != constants.UserAccessEnabledWebhookEvent && eventName != constants.UserAccessRevokedWebhookEvent && eventName != constants.UserDeactivatedWebhookEvent {
8 | return false
9 | }
10 |
11 | return true
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 10;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | color: #374151;
9 | font-size: 14px;
10 | }
11 |
12 | *,
13 | *:before,
14 | *:after {
15 | box-sizing: inherit;
16 | }
17 |
18 | .container {
19 | box-sizing: content-box;
20 | border: 1px solid #d1d5db;
21 | padding: 25px 20px;
22 | border-radius: 5px;
23 | }
24 |
25 | @media only screen and (min-width: 768px) {
26 | .container {
27 | width: 400px;
28 | margin: 0 auto;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/server/crypto/hmac.go:
--------------------------------------------------------------------------------
1 | package crypto
2 |
3 | import (
4 | "github.com/google/uuid"
5 | )
6 |
7 | // NewHMAC key returns new key that can be used to ecnrypt data using HMAC algo
8 | // returns key, string, error
9 | func NewHMACKey(algo, keyID string) (string, string, error) {
10 | key := uuid.New().String()
11 | jwkPublicKey, err := GetPubJWK(algo, keyID, []byte(key))
12 | if err != nil {
13 | return "", "", err
14 | }
15 | return key, string(jwkPublicKey), nil
16 | }
17 |
18 | // IsHMACValid checks if given string is valid HMCA algo
19 | func IsHMACA(algo string) bool {
20 | switch algo {
21 | case "HS256", "HS384", "HS512":
22 | return true
23 | default:
24 | return false
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/server/handlers/graphql.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "github.com/99designs/gqlgen/graphql/handler"
5 | "github.com/authorizerdev/authorizer/server/graph"
6 | "github.com/authorizerdev/authorizer/server/graph/generated"
7 | "github.com/gin-gonic/gin"
8 | )
9 |
10 | // GraphqlHandler is the main handler that handels all the graphql requests
11 | func GraphqlHandler() gin.HandlerFunc {
12 | // NewExecutableSchema and Config are in the generated.go file
13 | // Resolver is in the resolver.go file
14 | h := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))
15 |
16 | return func(c *gin.Context) {
17 | h.ServeHTTP(c.Writer, c.Request)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea or enhancement for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 | ---
8 |
9 | **Feature Description**
10 |
11 |
12 |
13 | **Describe the solution you'd like**
14 |
15 |
16 |
17 | **Describe alternatives you've considered**
18 |
19 |
20 |
21 | **Additional context**
22 |
23 |
24 |
--------------------------------------------------------------------------------
/server/db/providers/provider_template/session.go:
--------------------------------------------------------------------------------
1 | package provider_template
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/authorizerdev/authorizer/server/db/models"
8 | "github.com/google/uuid"
9 | )
10 |
11 | // AddSession to save session information in database
12 | func (p *provider) AddSession(ctx context.Context, session *models.Session) error {
13 | if session.ID == "" {
14 | session.ID = uuid.New().String()
15 | }
16 | session.CreatedAt = time.Now().Unix()
17 | session.UpdatedAt = time.Now().Unix()
18 | return nil
19 | }
20 |
21 | // DeleteSession to delete session information from database
22 | func (p *provider) DeleteSession(ctx context.Context, userId string) error {
23 | return nil
24 | }
25 |
--------------------------------------------------------------------------------
/server/test/urls_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/authorizerdev/authorizer/server/parsers"
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestGetHostName(t *testing.T) {
11 | url := "http://test.herokuapp.com:80"
12 |
13 | host, port := parsers.GetHostParts(url)
14 | expectedHost := "test.herokuapp.com"
15 |
16 | assert.Equal(t, host, expectedHost, "hostname should be equal")
17 | assert.Equal(t, port, "80", "port should be 80")
18 | }
19 |
20 | func TestGetDomainName(t *testing.T) {
21 | url := "http://test.herokuapp.com"
22 |
23 | got := parsers.GetDomainName(url)
24 | want := "herokuapp.com"
25 |
26 | assert.Equal(t, got, want, "domain name should be equal")
27 | }
28 |
--------------------------------------------------------------------------------
/server/test/meta_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/authorizerdev/authorizer/server/resolvers"
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func metaTests(t *testing.T, s TestSetup) {
12 | t.Helper()
13 | t.Run(`should get meta information`, func(t *testing.T) {
14 | ctx := context.Background()
15 | meta, err := resolvers.MetaResolver(ctx)
16 | assert.Nil(t, err)
17 | assert.False(t, meta.IsFacebookLoginEnabled)
18 | assert.False(t, meta.IsGoogleLoginEnabled)
19 | assert.False(t, meta.IsGithubLoginEnabled)
20 | assert.True(t, meta.IsEmailVerificationEnabled)
21 | assert.True(t, meta.IsBasicAuthenticationEnabled)
22 | assert.True(t, meta.IsMagicLinkLoginEnabled)
23 | })
24 | }
25 |
--------------------------------------------------------------------------------
/server/handlers/dashboard.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/authorizerdev/authorizer/server/constants"
7 | "github.com/authorizerdev/authorizer/server/memorystore"
8 | "github.com/gin-gonic/gin"
9 | )
10 |
11 | // DashboardHandler is the handler for the /dashboard route
12 | func DashboardHandler() gin.HandlerFunc {
13 | return func(c *gin.Context) {
14 | isOnboardingCompleted := false
15 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
16 | if err != nil || adminSecret != "" {
17 | isOnboardingCompleted = true
18 | }
19 |
20 | c.HTML(http.StatusOK, "dashboard.tmpl", gin.H{
21 | "data": map[string]interface{}{
22 | "isOnboardingCompleted": isOnboardingCompleted,
23 | },
24 | })
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/server/memorystore/providers/inmemory/provider.go:
--------------------------------------------------------------------------------
1 | package inmemory
2 |
3 | import (
4 | "sync"
5 |
6 | "github.com/authorizerdev/authorizer/server/memorystore/providers/inmemory/stores"
7 | )
8 |
9 | type provider struct {
10 | mutex sync.Mutex
11 | sessionStore *stores.SessionStore
12 | mfasessionStore *stores.SessionStore
13 | stateStore *stores.StateStore
14 | envStore *stores.EnvStore
15 | }
16 |
17 | // NewInMemoryStore returns a new in-memory store.
18 | func NewInMemoryProvider() (*provider, error) {
19 | return &provider{
20 | mutex: sync.Mutex{},
21 | envStore: stores.NewEnvStore(),
22 | sessionStore: stores.NewSessionStore(),
23 | mfasessionStore: stores.NewSessionStore(),
24 | stateStore: stores.NewStateStore(),
25 | }, nil
26 | }
27 |
--------------------------------------------------------------------------------
/server/db/providers/dynamodb/session.go:
--------------------------------------------------------------------------------
1 | package dynamodb
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/authorizerdev/authorizer/server/db/models"
8 | "github.com/google/uuid"
9 | )
10 |
11 | // AddSession to save session information in database
12 | func (p *provider) AddSession(ctx context.Context, session *models.Session) error {
13 | collection := p.db.Table(models.Collections.Session)
14 | if session.ID == "" {
15 | session.ID = uuid.New().String()
16 | }
17 | session.CreatedAt = time.Now().Unix()
18 | session.UpdatedAt = time.Now().Unix()
19 | err := collection.Put(session).RunWithContext(ctx)
20 | return err
21 | }
22 |
23 | // DeleteSession to delete session information from database
24 | func (p *provider) DeleteSession(ctx context.Context, userId string) error {
25 | return nil
26 | }
27 |
--------------------------------------------------------------------------------
/server/test/cors_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "net/http"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestCors(t *testing.T) {
11 | allowedOrigin := "http://localhost:8080" // The allowed origin that you want to check
12 | notAllowedOrigin := "http://myapp.com"
13 |
14 | s := testSetup()
15 | defer s.Server.Close()
16 | client := &http.Client{}
17 |
18 | req, _ := createContext(s)
19 | req.Header.Add("Origin", allowedOrigin)
20 | res, _ := client.Do(req)
21 |
22 | // You should get your origin (or a * depending on your config) if the
23 | // passed origin is allowed.
24 | o := res.Header.Get("Access-Control-Allow-Origin")
25 | assert.NotEqual(t, o, notAllowedOrigin, "Origins should not match")
26 | assert.Equal(t, o, allowedOrigin, "Origins do match")
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/utils/common.ts:
--------------------------------------------------------------------------------
1 | export const getCrypto = () => {
2 | //ie 11.x uses msCrypto
3 | return (window.crypto || (window as any).msCrypto) as Crypto;
4 | };
5 |
6 | export const createRandomString = () => {
7 | const charset =
8 | '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_~.';
9 | let random = '';
10 | const randomValues = Array.from(
11 | getCrypto().getRandomValues(new Uint8Array(43)),
12 | );
13 | randomValues.forEach((v) => (random += charset[v % charset.length]));
14 | return random;
15 | };
16 |
17 | export const createQueryParams = (params: any) => {
18 | return Object.keys(params)
19 | .filter((k) => typeof params[k] !== 'undefined')
20 | .map((k) => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
21 | .join('&');
22 | };
23 |
24 | export const hasWindow = (): boolean => typeof window !== 'undefined';
25 |
--------------------------------------------------------------------------------
/app/src/pages/signup.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { AuthorizerSignup, AuthorizerSocialLogin } from '@authorizerdev/authorizer-react';
3 | import styled from 'styled-components';
4 | import { Link } from 'react-router-dom';
5 |
6 | const FooterContent = styled.div`
7 | display: flex;
8 | justify-content: center;
9 | align-items: center;
10 | margin-top: 20px;
11 | `;
12 |
13 | export default function SignUp({
14 | urlProps,
15 | }: {
16 | urlProps: Record;
17 | }) {
18 | return (
19 |
20 | Sign Up
21 |
22 |
23 |
24 |
25 | Already have an account? Login
26 |
27 |
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/server/db/providers/provider_template/otp.go:
--------------------------------------------------------------------------------
1 | package provider_template
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/authorizerdev/authorizer/server/db/models"
7 | )
8 |
9 | // UpsertOTP to add or update otp
10 | func (p *provider) UpsertOTP(ctx context.Context, otp *models.OTP) (*models.OTP, error) {
11 | return nil, nil
12 | }
13 |
14 | // GetOTPByEmail to get otp for a given email address
15 | func (p *provider) GetOTPByEmail(ctx context.Context, emailAddress string) (*models.OTP, error) {
16 | return nil, nil
17 | }
18 |
19 | // GetOTPByPhoneNumber to get otp for a given phone number
20 | func (p *provider) GetOTPByPhoneNumber(ctx context.Context, phoneNumber string) (*models.OTP, error) {
21 | return nil, nil
22 | }
23 |
24 | // DeleteOTP to delete otp
25 | func (p *provider) DeleteOTP(ctx context.Context, otp *models.OTP) error {
26 | return nil
27 | }
28 |
--------------------------------------------------------------------------------
/templates/app.tmpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{.data.organizationName}}
6 |
7 |
8 |
9 |
10 |
11 | Document
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/server/utils/pagination.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "github.com/authorizerdev/authorizer/server/constants"
5 | "github.com/authorizerdev/authorizer/server/graph/model"
6 | )
7 |
8 | // GetPagination helps getting pagination data from paginated input
9 | // also returns default limit and offset if pagination data is not present
10 | func GetPagination(paginatedInput *model.PaginatedInput) *model.Pagination {
11 | limit := int64(constants.DefaultLimit)
12 | page := int64(1)
13 | if paginatedInput != nil && paginatedInput.Pagination != nil {
14 | if paginatedInput.Pagination.Limit != nil {
15 | limit = *paginatedInput.Pagination.Limit
16 | }
17 |
18 | if paginatedInput.Pagination.Page != nil {
19 | page = *paginatedInput.Pagination.Page
20 | }
21 | }
22 |
23 | return &model.Pagination{
24 | Limit: limit,
25 | Offset: (page - 1) * limit,
26 | Page: page,
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/server/utils/pkce.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "crypto/sha256"
5 | b64 "encoding/base64"
6 | "math/rand"
7 | "strings"
8 | "time"
9 | )
10 |
11 | const (
12 | length = 32
13 | )
14 |
15 | // GenerateCodeChallenge creates PKCE-Code-Challenge
16 | // and returns the verifier and challenge
17 | func GenerateCodeChallenge() (string, string) {
18 | // Generate Verifier
19 | randGenerator := rand.New(rand.NewSource(time.Now().UnixNano()))
20 | randomBytes := make([]byte, length)
21 | for i := 0; i < length; i++ {
22 | randomBytes[i] = byte(randGenerator.Intn(255))
23 | }
24 | verifier := strings.Trim(b64.URLEncoding.EncodeToString(randomBytes), "=")
25 |
26 | // Generate Challenge
27 | rawChallenge := sha256.New()
28 | rawChallenge.Write([]byte(verifier))
29 | challenge := strings.Trim(b64.URLEncoding.EncodeToString(rawChallenge.Sum(nil)), "=")
30 |
31 | return verifier, challenge
32 | }
33 |
--------------------------------------------------------------------------------
/server/db/providers/arangodb/session.go:
--------------------------------------------------------------------------------
1 | package arangodb
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/authorizerdev/authorizer/server/db/models"
8 | "github.com/google/uuid"
9 | )
10 |
11 | // AddSession to save session information in database
12 | func (p *provider) AddSession(ctx context.Context, session *models.Session) error {
13 | if session.ID == "" {
14 | session.ID = uuid.New().String()
15 | session.Key = session.ID
16 | }
17 | session.CreatedAt = time.Now().Unix()
18 | session.UpdatedAt = time.Now().Unix()
19 | sessionCollection, _ := p.db.Collection(ctx, models.Collections.Session)
20 | _, err := sessionCollection.CreateDocument(ctx, session)
21 | if err != nil {
22 | return err
23 | }
24 | return nil
25 | }
26 |
27 | // DeleteSession to delete session information from database
28 | func (p *provider) DeleteSession(ctx context.Context, userId string) error {
29 | return nil
30 | }
31 |
--------------------------------------------------------------------------------
/server/db/providers/sql/session.go:
--------------------------------------------------------------------------------
1 | package sql
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/authorizerdev/authorizer/server/db/models"
8 | "github.com/google/uuid"
9 | "gorm.io/gorm/clause"
10 | )
11 |
12 | // AddSession to save session information in database
13 | func (p *provider) AddSession(ctx context.Context, session *models.Session) error {
14 | if session.ID == "" {
15 | session.ID = uuid.New().String()
16 | }
17 |
18 | session.Key = session.ID
19 | session.CreatedAt = time.Now().Unix()
20 | session.UpdatedAt = time.Now().Unix()
21 | res := p.db.Clauses(
22 | clause.OnConflict{
23 | DoNothing: true,
24 | }).Create(&session)
25 | if res.Error != nil {
26 | return res.Error
27 | }
28 | return nil
29 | }
30 |
31 | // DeleteSession to delete session information from database
32 | func (p *provider) DeleteSession(ctx context.Context, userId string) error {
33 | return nil
34 | }
35 |
--------------------------------------------------------------------------------
/server/db/providers/provider_template/webhook_log.go:
--------------------------------------------------------------------------------
1 | package provider_template
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/authorizerdev/authorizer/server/db/models"
8 | "github.com/authorizerdev/authorizer/server/graph/model"
9 | "github.com/google/uuid"
10 | )
11 |
12 | // AddWebhookLog to add webhook log
13 | func (p *provider) AddWebhookLog(ctx context.Context, webhookLog *models.WebhookLog) (*model.WebhookLog, error) {
14 | if webhookLog.ID == "" {
15 | webhookLog.ID = uuid.New().String()
16 | }
17 |
18 | webhookLog.Key = webhookLog.ID
19 | webhookLog.CreatedAt = time.Now().Unix()
20 | webhookLog.UpdatedAt = time.Now().Unix()
21 | return webhookLog.AsAPIWebhookLog(), nil
22 | }
23 |
24 | // ListWebhookLogs to list webhook logs
25 | func (p *provider) ListWebhookLogs(ctx context.Context, pagination *model.Pagination, webhookID string) (*model.WebhookLogs, error) {
26 | return nil, nil
27 | }
28 |
--------------------------------------------------------------------------------
/server/constants/webhook_event.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | const (
4 |
5 | // UserLoginWebhookEvent name for login event
6 | UserLoginWebhookEvent = `user.login`
7 | // UserCreatedWebhookEvent name for user creation event
8 | // This is triggered when user entry is created but still not verified
9 | UserCreatedWebhookEvent = `user.created`
10 | // UserSignUpWebhookEvent name for signup event
11 | UserSignUpWebhookEvent = `user.signup`
12 | // UserAccessRevokedWebhookEvent name for user access revoke event
13 | UserAccessRevokedWebhookEvent = `user.access_revoked`
14 | // UserAccessEnabledWebhookEvent name for user access enable event
15 | UserAccessEnabledWebhookEvent = `user.access_enabled`
16 | // UserDeletedWebhookEvent name for user deleted event
17 | UserDeletedWebhookEvent = `user.deleted`
18 | // UserDeactivatedWebhookEvent name for user deactivated event
19 | UserDeactivatedWebhookEvent = `user.deactivated`
20 | )
21 |
--------------------------------------------------------------------------------
/server/utils/nonce.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "github.com/google/uuid"
5 |
6 | "github.com/authorizerdev/authorizer/server/crypto"
7 | )
8 |
9 | // GenerateNonce generats random nonce string and returns
10 | // the nonce string, nonce hash, error
11 | func GenerateNonce() (string, string, error) {
12 | nonce := uuid.New().String()
13 | nonceHash, err := crypto.EncryptAES(nonce)
14 | if err != nil {
15 | return "", "", err
16 | }
17 | return nonce, nonceHash, err
18 | }
19 |
20 | // EncryptNonce nonce string
21 | func EncryptNonce(nonce string) (string, error) {
22 | nonceHash, err := crypto.EncryptAES(nonce)
23 | if err != nil {
24 | return "", err
25 | }
26 | return nonceHash, err
27 | }
28 |
29 | // DecryptNonce nonce string
30 | func DecryptNonce(nonceHash string) (string, error) {
31 | nonce, err := crypto.DecryptAES(nonceHash)
32 | if err != nil {
33 | return "", err
34 | }
35 | return nonce, err
36 | }
37 |
--------------------------------------------------------------------------------
/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js",
8 | "start": "NODE_ENV=development node ./esbuild.config.js",
9 | "format": "prettier --write 'src/**/*.(ts|tsx|js|jsx)'"
10 | },
11 | "keywords": [],
12 | "author": "Lakhan Samani",
13 | "license": "ISC",
14 | "dependencies": {
15 | "@authorizerdev/authorizer-react": "^1.3.2",
16 | "@types/react": "^17.0.15",
17 | "@types/react-dom": "^17.0.9",
18 | "esbuild": "^0.12.17",
19 | "react": "^17.0.2",
20 | "react-dom": "^17.0.2",
21 | "react-is": "^17.0.2",
22 | "react-router-dom": "^5.2.0",
23 | "styled-components": "^5.3.0",
24 | "typescript": "^4.3.5"
25 | },
26 | "devDependencies": {
27 | "@types/react-router-dom": "^5.1.8",
28 | "@types/styled-components": "^5.1.11",
29 | "prettier": "2.7.1"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/server/token/verification_token.go:
--------------------------------------------------------------------------------
1 | package token
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/authorizerdev/authorizer/server/constants"
7 | "github.com/authorizerdev/authorizer/server/memorystore"
8 | "github.com/golang-jwt/jwt"
9 | )
10 |
11 | // CreateVerificationToken creates a verification JWT token
12 | func CreateVerificationToken(email, tokenType, hostname, nonceHash, redirectURL string) (string, error) {
13 | clientID, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID)
14 | if err != nil {
15 | return "", err
16 | }
17 | claims := jwt.MapClaims{
18 | "iss": hostname,
19 | "aud": clientID,
20 | "sub": email,
21 | "exp": time.Now().Add(time.Minute * 30).Unix(),
22 | "iat": time.Now().Unix(),
23 | "token_type": tokenType,
24 | "nonce": nonceHash,
25 | "redirect_uri": redirectURL,
26 | }
27 |
28 | return SignJWTToken(claims)
29 | }
30 |
--------------------------------------------------------------------------------
/server/db/models/session.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | // Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
4 |
5 | // Session model for db
6 | type Session struct {
7 | Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
8 | ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
9 | UserID string `gorm:"type:char(36)" json:"user_id" bson:"user_id" cql:"user_id" dynamo:"user_id" index:"user_id,hash"`
10 | UserAgent string `json:"user_agent" bson:"user_agent" cql:"user_agent" dynamo:"user_agent"`
11 | IP string `json:"ip" bson:"ip" cql:"ip" dynamo:"ip"`
12 | CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
13 | UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
14 | }
15 |
--------------------------------------------------------------------------------
/server/db/providers/couchbase/session.go:
--------------------------------------------------------------------------------
1 | package couchbase
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/authorizerdev/authorizer/server/db/models"
8 | "github.com/couchbase/gocb/v2"
9 | "github.com/google/uuid"
10 | )
11 |
12 | // AddSession to save session information in database
13 | func (p *provider) AddSession(ctx context.Context, session *models.Session) error {
14 | if session.ID == "" {
15 | session.ID = uuid.New().String()
16 | }
17 | session.CreatedAt = time.Now().Unix()
18 | session.UpdatedAt = time.Now().Unix()
19 | insertOpt := gocb.InsertOptions{
20 | Context: ctx,
21 | }
22 | _, err := p.db.Collection(models.Collections.Session).Insert(session.ID, session, &insertOpt)
23 | if err != nil {
24 | return err
25 | }
26 | return nil
27 | }
28 |
29 | // DeleteSession to delete session information from database
30 | func (p *provider) DeleteSession(ctx context.Context, userId string) error {
31 | return nil
32 | }
33 |
--------------------------------------------------------------------------------
/server/authenticators/totp_store.go:
--------------------------------------------------------------------------------
1 | package authenticators
2 |
3 | import (
4 | "github.com/authorizerdev/authorizer/server/authenticators/providers"
5 | "github.com/authorizerdev/authorizer/server/authenticators/providers/totp"
6 | "github.com/authorizerdev/authorizer/server/constants"
7 | "github.com/authorizerdev/authorizer/server/memorystore"
8 | )
9 |
10 | // Provider is the global authenticators provider.
11 | var Provider providers.Provider
12 |
13 | // InitTOTPStore initializes the TOTP authenticator store if it's not disabled in the environment variables.
14 | // It sets the global Provider variable to a new TOTP provider.
15 | func InitTOTPStore() error {
16 | var err error
17 | isTOTPEnvServiceDisabled, _ := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableTOTPLogin)
18 |
19 | if !isTOTPEnvServiceDisabled {
20 | Provider, err = totp.NewProvider()
21 | if err != nil {
22 | return err
23 | }
24 | }
25 | return nil
26 | }
27 |
--------------------------------------------------------------------------------
/server/db/providers/provider_template/env.go:
--------------------------------------------------------------------------------
1 | package provider_template
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/authorizerdev/authorizer/server/db/models"
8 | "github.com/google/uuid"
9 | )
10 |
11 | // AddEnv to save environment information in database
12 | func (p *provider) AddEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
13 | if env.ID == "" {
14 | env.ID = uuid.New().String()
15 | }
16 |
17 | env.CreatedAt = time.Now().Unix()
18 | env.UpdatedAt = time.Now().Unix()
19 | return env, nil
20 | }
21 |
22 | // UpdateEnv to update environment information in database
23 | func (p *provider) UpdateEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
24 | env.UpdatedAt = time.Now().Unix()
25 | return env, nil
26 | }
27 |
28 | // GetEnv to get environment information from database
29 | func (p *provider) GetEnv(ctx context.Context) (*models.Env, error) {
30 | var env *models.Env
31 |
32 | return env, nil
33 | }
34 |
--------------------------------------------------------------------------------
/server/handlers/jwks.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/gin-gonic/gin"
7 | log "github.com/sirupsen/logrus"
8 |
9 | "github.com/authorizerdev/authorizer/server/constants"
10 | "github.com/authorizerdev/authorizer/server/memorystore"
11 | )
12 |
13 | func JWKsHandler() gin.HandlerFunc {
14 | return func(c *gin.Context) {
15 | var data map[string]string
16 | jwk, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJWK)
17 | if err != nil {
18 | log.Debug("Error getting JWK from memorystore: ", err)
19 | c.JSON(500, gin.H{
20 | "error": err.Error(),
21 | })
22 | return
23 | }
24 | err = json.Unmarshal([]byte(jwk), &data)
25 | if err != nil {
26 | log.Debug("Failed to parse JWK: ", err)
27 | c.JSON(500, gin.H{
28 | "error": err.Error(),
29 | })
30 | return
31 | }
32 | c.JSON(200, gin.H{
33 | "keys": []map[string]string{
34 | data,
35 | },
36 | })
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/server/db/providers/mongodb/session.go:
--------------------------------------------------------------------------------
1 | package mongodb
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/authorizerdev/authorizer/server/db/models"
8 | "github.com/google/uuid"
9 | "go.mongodb.org/mongo-driver/mongo/options"
10 | )
11 |
12 | // AddSession to save session information in database
13 | func (p *provider) AddSession(ctx context.Context, session *models.Session) error {
14 | if session.ID == "" {
15 | session.ID = uuid.New().String()
16 | }
17 |
18 | session.Key = session.ID
19 | session.CreatedAt = time.Now().Unix()
20 | session.UpdatedAt = time.Now().Unix()
21 | sessionCollection := p.db.Collection(models.Collections.Session, options.Collection())
22 | _, err := sessionCollection.InsertOne(ctx, session)
23 | if err != nil {
24 | return err
25 | }
26 | return nil
27 | }
28 |
29 | // DeleteSession to delete session information from database
30 | func (p *provider) DeleteSession(ctx context.Context, userId string) error {
31 | return nil
32 | }
33 |
--------------------------------------------------------------------------------
/server/middlewares/client_check.go:
--------------------------------------------------------------------------------
1 | package middlewares
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/gin-gonic/gin"
7 | log "github.com/sirupsen/logrus"
8 |
9 | "github.com/authorizerdev/authorizer/server/constants"
10 | "github.com/authorizerdev/authorizer/server/memorystore"
11 | )
12 |
13 | // ClientCheckMiddleware is a middleware to verify the client ID
14 | // Note: client ID is passed in the header
15 | func ClientCheckMiddleware() gin.HandlerFunc {
16 | return func(c *gin.Context) {
17 | clientID := c.Request.Header.Get("X-Authorizer-Client-ID")
18 | if client, _ := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID); clientID != "" && client != "" && client != clientID {
19 | log.Debug("Client ID is invalid: ", clientID)
20 | c.JSON(http.StatusBadRequest, gin.H{
21 | "error": "invalid_client_id",
22 | "error_description": "The client id is invalid",
23 | })
24 | return
25 | }
26 |
27 | c.Next()
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/server/test/admin_login_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/authorizerdev/authorizer/server/constants"
7 | "github.com/authorizerdev/authorizer/server/graph/model"
8 | "github.com/authorizerdev/authorizer/server/memorystore"
9 | "github.com/authorizerdev/authorizer/server/resolvers"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func adminLoginTests(t *testing.T, s TestSetup) {
14 | t.Helper()
15 | t.Run(`should complete admin login`, func(t *testing.T) {
16 | _, ctx := createContext(s)
17 | _, err := resolvers.AdminLoginResolver(ctx, model.AdminLoginInput{
18 | AdminSecret: "admin_test",
19 | })
20 |
21 | assert.NotNil(t, err)
22 |
23 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
24 | assert.Nil(t, err)
25 | _, err = resolvers.AdminLoginResolver(ctx, model.AdminLoginInput{
26 | AdminSecret: adminSecret,
27 | })
28 |
29 | assert.Nil(t, err)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/server/test/admin_signup_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/authorizerdev/authorizer/server/constants"
7 | "github.com/authorizerdev/authorizer/server/graph/model"
8 | "github.com/authorizerdev/authorizer/server/memorystore"
9 | "github.com/authorizerdev/authorizer/server/resolvers"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func adminSignupTests(t *testing.T, s TestSetup) {
14 | t.Helper()
15 | t.Run(`should complete admin signup`, func(t *testing.T) {
16 | _, ctx := createContext(s)
17 | _, err := resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{
18 | AdminSecret: "admin",
19 | })
20 | assert.NotNil(t, err)
21 | // reset env for test to pass
22 | err = memorystore.Provider.UpdateEnvVariable(constants.EnvKeyAdminSecret, "")
23 | assert.Nil(t, err)
24 | _, err = resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{
25 | AdminSecret: "admin123",
26 | })
27 | assert.NoError(t, err)
28 | })
29 | }
30 |
--------------------------------------------------------------------------------
/server/resolvers/admin_logout.go:
--------------------------------------------------------------------------------
1 | package resolvers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | log "github.com/sirupsen/logrus"
8 |
9 | "github.com/authorizerdev/authorizer/server/cookie"
10 | "github.com/authorizerdev/authorizer/server/graph/model"
11 | "github.com/authorizerdev/authorizer/server/token"
12 | "github.com/authorizerdev/authorizer/server/utils"
13 | )
14 |
15 | // AdminLogoutResolver is a resolver for admin logout mutation
16 | func AdminLogoutResolver(ctx context.Context) (*model.Response, error) {
17 | var res *model.Response
18 |
19 | gc, err := utils.GinContextFromContext(ctx)
20 | if err != nil {
21 | log.Debug("Failed to get GinContext: ", err)
22 | return res, err
23 | }
24 |
25 | if !token.IsSuperAdmin(gc) {
26 | log.Debug("Admin is not logged in")
27 | return res, fmt.Errorf("unauthorized")
28 | }
29 |
30 | cookie.DeleteAdminCookie(gc)
31 |
32 | res = &model.Response{
33 | Message: "admin logged out successfully",
34 | }
35 | return res, nil
36 | }
37 |
--------------------------------------------------------------------------------
/server/middlewares/cors.go:
--------------------------------------------------------------------------------
1 | package middlewares
2 |
3 | import (
4 | "github.com/authorizerdev/authorizer/server/validators"
5 | "github.com/gin-gonic/gin"
6 | )
7 |
8 | // CORSMiddleware is a middleware to add cors headers
9 | func CORSMiddleware() gin.HandlerFunc {
10 | return func(c *gin.Context) {
11 | origin := c.Request.Header.Get("Origin")
12 | if validators.IsValidOrigin(origin) {
13 | c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
14 | }
15 |
16 | c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
17 | c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With, X-authorizer-url, X-Forwarded-Proto, X-authorizer-client-id")
18 | c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
19 |
20 | if c.Request.Method == "OPTIONS" {
21 | c.AbortWithStatus(204)
22 | return
23 | }
24 |
25 | c.Next()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/pages/dashboard.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useAuthorizer } from '@authorizerdev/authorizer-react';
3 |
4 | export default function Dashboard() {
5 | const [loading, setLoading] = React.useState(false);
6 | const { user, setToken, authorizerRef } = useAuthorizer();
7 |
8 | const onLogout = async () => {
9 | setLoading(true);
10 | await authorizerRef.logout();
11 | setToken(null);
12 | setLoading(false);
13 | };
14 |
15 | return (
16 |
17 |
Hey 👋,
18 |
Thank you for using authorizer.
19 |
20 | Your email address is{' '}
21 |
22 | {user?.email}
23 |
24 |
25 |
26 |
27 | {loading ? (
28 |
Processing....
29 | ) : (
30 |
37 | Logout
38 |
39 | )}
40 |
41 | );
42 | }
43 |
--------------------------------------------------------------------------------
/server/test/admin_logout_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/authorizerdev/authorizer/server/constants"
8 | "github.com/authorizerdev/authorizer/server/crypto"
9 | "github.com/authorizerdev/authorizer/server/memorystore"
10 | "github.com/authorizerdev/authorizer/server/resolvers"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | func adminLogoutTests(t *testing.T, s TestSetup) {
15 | t.Helper()
16 | t.Run(`should get admin session`, func(t *testing.T) {
17 | req, ctx := createContext(s)
18 | _, err := resolvers.AdminLogoutResolver(ctx)
19 | assert.NotNil(t, err)
20 |
21 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
22 | assert.Nil(t, err)
23 |
24 | h, err := crypto.EncryptPassword(adminSecret)
25 | assert.Nil(t, err)
26 | req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
27 | _, err = resolvers.AdminLogoutResolver(ctx)
28 |
29 | assert.Nil(t, err)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/server/resolvers/webhook.go:
--------------------------------------------------------------------------------
1 | package resolvers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/authorizerdev/authorizer/server/db"
8 | "github.com/authorizerdev/authorizer/server/graph/model"
9 | "github.com/authorizerdev/authorizer/server/token"
10 | "github.com/authorizerdev/authorizer/server/utils"
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | // WebhookResolver resolver for getting webhook by identifier
15 | func WebhookResolver(ctx context.Context, params model.WebhookRequest) (*model.Webhook, error) {
16 | gc, err := utils.GinContextFromContext(ctx)
17 | if err != nil {
18 | log.Debug("Failed to get GinContext: ", err)
19 | return nil, err
20 | }
21 |
22 | if !token.IsSuperAdmin(gc) {
23 | log.Debug("Not logged in as super admin")
24 | return nil, fmt.Errorf("unauthorized")
25 | }
26 |
27 | webhook, err := db.Provider.GetWebhookByID(ctx, params.ID)
28 | if err != nil {
29 | log.Debug("error getting webhook: ", err)
30 | return nil, err
31 | }
32 | return webhook, nil
33 | }
34 |
--------------------------------------------------------------------------------
/server/test/admin_session_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/authorizerdev/authorizer/server/constants"
8 | "github.com/authorizerdev/authorizer/server/crypto"
9 | "github.com/authorizerdev/authorizer/server/memorystore"
10 | "github.com/authorizerdev/authorizer/server/resolvers"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | func adminSessionTests(t *testing.T, s TestSetup) {
15 | t.Helper()
16 | t.Run(`should get admin session`, func(t *testing.T) {
17 | req, ctx := createContext(s)
18 | _, err := resolvers.AdminSessionResolver(ctx)
19 | assert.NotNil(t, err)
20 |
21 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
22 | assert.Nil(t, err)
23 |
24 | h, err := crypto.EncryptPassword(adminSecret)
25 | assert.Nil(t, err)
26 | req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
27 | _, err = resolvers.AdminSessionResolver(ctx)
28 |
29 | assert.Nil(t, err)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/server/db/models/env.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | // Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
4 |
5 | // Env model for db
6 | type Env struct {
7 | Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
8 | ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
9 | EnvData string `json:"env" bson:"env" cql:"env" dynamo:"env"`
10 | Hash string `json:"hash" bson:"hash" cql:"hash" dynamo:"hash"`
11 | EncryptionKey string `json:"encryption_key" bson:"encryption_key" cql:"encryption_key" dynamo:"encryption_key"` // couchbase has "hash" as reserved keyword so we cannot use it. This will be empty for other dbs.
12 | UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
13 | CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
14 | }
15 |
--------------------------------------------------------------------------------
/server/memorystore/providers/inmemory/stores/state_store.go:
--------------------------------------------------------------------------------
1 | package stores
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | // StateStore struct to store the env variables
8 | type StateStore struct {
9 | mutex sync.Mutex
10 | store map[string]string
11 | }
12 |
13 | // NewStateStore create a new state store
14 | func NewStateStore() *StateStore {
15 | return &StateStore{
16 | mutex: sync.Mutex{},
17 | store: make(map[string]string),
18 | }
19 | }
20 |
21 | // Get returns the value of the key in state store
22 | func (s *StateStore) Get(key string) string {
23 | s.mutex.Lock()
24 | defer s.mutex.Unlock()
25 | return s.store[key]
26 | }
27 |
28 | // Set sets the value of the key in state store
29 | func (s *StateStore) Set(key string, value string) {
30 | s.mutex.Lock()
31 | defer s.mutex.Unlock()
32 |
33 | s.store[key] = value
34 | }
35 |
36 | // Remove removes the key from state store
37 | func (s *StateStore) Remove(key string) {
38 | s.mutex.Lock()
39 | defer s.mutex.Unlock()
40 |
41 | delete(s.store, key)
42 | }
43 |
--------------------------------------------------------------------------------
/server/test/env_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/authorizerdev/authorizer/server/constants"
8 | "github.com/authorizerdev/authorizer/server/crypto"
9 | "github.com/authorizerdev/authorizer/server/memorystore"
10 | "github.com/authorizerdev/authorizer/server/resolvers"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | func envTests(t *testing.T, s TestSetup) {
15 | t.Helper()
16 | t.Run(`should get envs`, func(t *testing.T) {
17 | req, ctx := createContext(s)
18 | _, err := resolvers.EnvResolver(ctx)
19 | assert.NotNil(t, err)
20 |
21 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
22 | assert.Nil(t, err)
23 |
24 | h, err := crypto.EncryptPassword(adminSecret)
25 | assert.Nil(t, err)
26 | req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
27 | res, err := resolvers.EnvResolver(ctx)
28 | assert.Nil(t, err)
29 | assert.Equal(t, *res.AdminSecret, adminSecret)
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/server/db/providers/dynamodb/shared.go:
--------------------------------------------------------------------------------
1 | package dynamodb
2 |
3 | import (
4 | "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
5 | "github.com/guregu/dynamo"
6 | )
7 |
8 | // As updpate all item not supported so set manually via Set and SetNullable for empty field
9 | func UpdateByHashKey(table dynamo.Table, hashKey string, hashValue string, item interface{}) error {
10 | existingValue, err := dynamo.MarshalItem(item)
11 | var i interface{}
12 | if err != nil {
13 | return err
14 | }
15 | nullableValue, err := dynamodbattribute.MarshalMap(item)
16 | if err != nil {
17 | return err
18 | }
19 | u := table.Update(hashKey, hashValue)
20 | for k, v := range existingValue {
21 | if k == hashKey {
22 | continue
23 | }
24 | u = u.Set(k, v)
25 | }
26 | for k, v := range nullableValue {
27 | if k == hashKey {
28 | continue
29 | }
30 | dynamodbattribute.Unmarshal(v, &i)
31 | if i == nil {
32 | u = u.SetNullable(k, v)
33 | }
34 | }
35 | err = u.Run()
36 | if err != nil {
37 | return err
38 | }
39 | return nil
40 | }
41 |
--------------------------------------------------------------------------------
/scripts/build-mac.sh:
--------------------------------------------------------------------------------
1 | VERSION="$1"
2 | make clean && make build-app && CGO_ENABLED=1 VERSION=${VERSION} make
3 | FILE_NAME=authorizer-${VERSION}-darwin-amd64.tar.gz
4 | tar cvfz ${FILE_NAME} .env app/build build templates dashboard/build
5 | AUTH="Authorization: token $GITHUB_TOKEN"
6 | RELASE_INFO=$(curl -sH "$AUTH" https://api.github.com/repos/authorizerdev/authorizer/releases/tags/${VERSION})
7 | echo $RELASE_INFO
8 |
9 | eval $(echo "$RELASE_INFO" | grep -m 1 "id.:" | grep -w id | tr : = | tr -cd '[[:alnum:]]=')
10 | [ "$id" ] || { echo "Error: Failed to get release id for tag: $VERSION"; echo "$RELASE_INFO" | awk 'length($0)<100' >&2; exit 1; }
11 | echo $id
12 | GH_ASSET="https://uploads.github.com/repos/authorizerdev/authorizer/releases/$id/assets?name=$(basename $FILE_NAME)"
13 |
14 | echo $GH_ASSET
15 |
16 | curl -H $AUTH -H "Content-Type: $(file -b --mime-type $FILE_NAME)" --data-binary @$FILE_NAME $GH_ASSET
17 |
18 | curl "$GITHUB_OAUTH_BASIC" --data-binary @"$FILE_NAME" -H "Authorization: token $GITHUB_TOKEN" -H "Content-Type: application/octet-stream" $GH_ASSET
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 | ---
8 |
9 | **Version:** x.y.z
10 |
11 |
12 |
13 | **Describe the bug**
14 |
15 |
16 |
17 | **Steps To Reproduce**
18 |
19 |
20 |
21 | **Expected behavior**
22 |
23 |
24 |
25 | **Screenshots**
26 |
27 |
28 |
29 | **Desktop (please complete the following information):**
30 |
31 | - OS: [e.g. iOS]
32 | - Browser [e.g. chrome, safari]
33 | - Version [e.g. 22]
34 |
35 | **Additional context**
36 |
37 |
38 |
--------------------------------------------------------------------------------
/server/utils/file.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "errors"
5 | "os"
6 | )
7 |
8 | // CreateFolder creates a folder in Current working dir
9 | func CreateFolder(dir string) (string, error) {
10 | pwd, err := os.Getwd()
11 | if err != nil {
12 | return "", err
13 | }
14 | path := pwd + "/" + dir
15 | err = os.Mkdir(path, 0o755)
16 | if err == nil {
17 | return path, nil
18 | }
19 | if os.IsExist(err) {
20 | // check that the existing path is a directory
21 | info, err := os.Stat(path)
22 | if err != nil {
23 | return "", err
24 | }
25 | if !info.IsDir() {
26 | return "", errors.New("path exists but is not a directory")
27 | }
28 | return path, nil
29 | }
30 | return path, err
31 | }
32 |
33 | // CreateFile creates a file on given path with given content
34 | func CreateFile(filePath string, content string) error {
35 | f, err := os.Create(filePath)
36 | if err != nil {
37 | return err
38 | }
39 |
40 | defer f.Close()
41 |
42 | _, err = f.WriteString(content)
43 |
44 | if err != nil {
45 | return err
46 | }
47 | return nil
48 | }
49 |
--------------------------------------------------------------------------------
/server/constants/oauth2.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | const (
4 | // - query: for Authorization Code grant. 302 Found triggers redirect.
5 | ResponseModeQuery = "query"
6 | // - fragment: for Implicit grant. 302 Found triggers redirect.
7 | ResponseModeFragment = "fragment"
8 | // - form_post: 200 OK with response parameters embedded in an HTML form as hidden parameters.
9 | ResponseModeFormPost = "form_post"
10 | // - web_message: For Silent Authentication. Uses HTML5 web messaging.
11 | ResponseModeWebMessage = "web_message"
12 |
13 | // For the Authorization Code grant, use response_type=code to include the authorization code.
14 | ResponseTypeCode = "code"
15 | // For the Implicit grant, use response_type=token to include an access token.
16 | ResponseTypeToken = "token"
17 | // For the Implicit grant of id_token, use response_type=id_token to include an identifier token.
18 | ResponseTypeIDToken = "id_token"
19 |
20 | // Constant indicating the "signup" screen hint for customizing authentication process and redirect to a signup page.
21 | ScreenHintSignUp = "signup"
22 | )
23 |
--------------------------------------------------------------------------------
/server/resolvers/users.go:
--------------------------------------------------------------------------------
1 | package resolvers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | log "github.com/sirupsen/logrus"
8 |
9 | "github.com/authorizerdev/authorizer/server/db"
10 | "github.com/authorizerdev/authorizer/server/graph/model"
11 | "github.com/authorizerdev/authorizer/server/token"
12 | "github.com/authorizerdev/authorizer/server/utils"
13 | )
14 |
15 | // UsersResolver is a resolver for users query
16 | // This is admin only query
17 | func UsersResolver(ctx context.Context, params *model.PaginatedInput) (*model.Users, error) {
18 | gc, err := utils.GinContextFromContext(ctx)
19 | if err != nil {
20 | log.Debug("Failed to get GinContext: ", err)
21 | return nil, err
22 | }
23 |
24 | if !token.IsSuperAdmin(gc) {
25 | log.Debug("Not logged in as super admin.")
26 | return nil, fmt.Errorf("unauthorized")
27 | }
28 |
29 | pagination := utils.GetPagination(params)
30 |
31 | res, err := db.Provider.ListUsers(ctx, pagination)
32 | if err != nil {
33 | log.Debug("Failed to get users: ", err)
34 | return nil, err
35 | }
36 |
37 | return res, nil
38 | }
39 |
--------------------------------------------------------------------------------
/server/authenticators/providers/providers.go:
--------------------------------------------------------------------------------
1 | package providers
2 |
3 | import "context"
4 |
5 | // AuthenticatorConfig defines authenticator config
6 | type AuthenticatorConfig struct {
7 | // ScannerImage is the base64 of QR code image
8 | ScannerImage string
9 | // Secrets is the secret key
10 | Secret string
11 | // RecoveryCode is the list of recovery codes
12 | RecoveryCodes []string
13 | // RecoveryCodeMap is the map of recovery codes
14 | RecoveryCodeMap map[string]bool
15 | }
16 |
17 | // Provider defines authenticators provider
18 | type Provider interface {
19 | // Generate totp: to generate totp, store secret into db and returns base64 of QR code image
20 | Generate(ctx context.Context, id string) (*AuthenticatorConfig, error)
21 | // Validate totp: user passcode with secret stored in our db
22 | Validate(ctx context.Context, passcode string, userID string) (bool, error)
23 | // ValidateRecoveryCode totp: allows user to validate using recovery code incase if they lost their device
24 | ValidateRecoveryCode(ctx context.Context, recoveryCode, userID string) (bool, error)
25 | }
26 |
--------------------------------------------------------------------------------
/server/resolvers/webhooks.go:
--------------------------------------------------------------------------------
1 | package resolvers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/authorizerdev/authorizer/server/db"
8 | "github.com/authorizerdev/authorizer/server/graph/model"
9 | "github.com/authorizerdev/authorizer/server/token"
10 | "github.com/authorizerdev/authorizer/server/utils"
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | // WebhooksResolver resolver for getting the list of webhooks based on pagination
15 | func WebhooksResolver(ctx context.Context, params *model.PaginatedInput) (*model.Webhooks, error) {
16 | gc, err := utils.GinContextFromContext(ctx)
17 | if err != nil {
18 | log.Debug("Failed to get GinContext: ", err)
19 | return nil, err
20 | }
21 |
22 | if !token.IsSuperAdmin(gc) {
23 | log.Debug("Not logged in as super admin")
24 | return nil, fmt.Errorf("unauthorized")
25 | }
26 |
27 | pagination := utils.GetPagination(params)
28 | webhooks, err := db.Provider.ListWebhook(ctx, pagination)
29 | if err != nil {
30 | log.Debug("failed to get webhooks: ", err)
31 | return nil, err
32 | }
33 | return webhooks, nil
34 | }
35 |
--------------------------------------------------------------------------------
/server/db/providers/cassandradb/session.go:
--------------------------------------------------------------------------------
1 | package cassandradb
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/authorizerdev/authorizer/server/db/models"
9 | "github.com/google/uuid"
10 | )
11 |
12 | // AddSession to save session information in database
13 | func (p *provider) AddSession(ctx context.Context, session *models.Session) error {
14 | if session.ID == "" {
15 | session.ID = uuid.New().String()
16 | }
17 | session.CreatedAt = time.Now().Unix()
18 | session.UpdatedAt = time.Now().Unix()
19 | insertSessionQuery := fmt.Sprintf("INSERT INTO %s (id, user_id, user_agent, ip, created_at, updated_at) VALUES ('%s', '%s', '%s', '%s', %d, %d)", KeySpace+"."+models.Collections.Session, session.ID, session.UserID, session.UserAgent, session.IP, session.CreatedAt, session.UpdatedAt)
20 | err := p.db.Query(insertSessionQuery).Exec()
21 | if err != nil {
22 | return err
23 | }
24 | return nil
25 | }
26 |
27 | // DeleteSession to delete session information from database
28 | func (p *provider) DeleteSession(ctx context.Context, userId string) error {
29 | return nil
30 | }
31 |
--------------------------------------------------------------------------------
/dashboard/src/layouts/DashboardLayout.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Drawer,
4 | DrawerContent,
5 | useDisclosure,
6 | useColorModeValue,
7 | } from '@chakra-ui/react';
8 | import React, { ReactNode } from 'react';
9 | import { Sidebar, MobileNav } from '../components/Menu';
10 |
11 | export function DashboardLayout({ children }: { children: ReactNode }) {
12 | const { isOpen, onOpen, onClose } = useDisclosure();
13 | return (
14 |
15 | onClose}
17 | display={{ base: 'none', md: 'block' }}
18 | />
19 |
28 |
29 |
30 |
31 |
32 | {/* mobilenav */}
33 |
34 |
35 | {children}
36 |
37 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/server/test/email_templates_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/authorizerdev/authorizer/server/constants"
8 | "github.com/authorizerdev/authorizer/server/crypto"
9 | "github.com/authorizerdev/authorizer/server/memorystore"
10 | "github.com/authorizerdev/authorizer/server/resolvers"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | func emailTemplatesTest(t *testing.T, s TestSetup) {
15 | t.Helper()
16 | t.Run("should get email templates", func(t *testing.T) {
17 | req, ctx := createContext(s)
18 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
19 | assert.NoError(t, err)
20 | h, err := crypto.EncryptPassword(adminSecret)
21 | assert.NoError(t, err)
22 | req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
23 |
24 | emailTemplates, err := resolvers.EmailTemplatesResolver(ctx, nil)
25 | assert.NoError(t, err)
26 | assert.NotEmpty(t, emailTemplates)
27 | assert.Len(t, emailTemplates.EmailTemplates, len(s.TestInfo.TestEmailTemplateEventTypes))
28 | })
29 | }
30 |
--------------------------------------------------------------------------------
/server/test/resend_verify_email_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/authorizerdev/authorizer/server/constants"
7 | "github.com/authorizerdev/authorizer/server/graph/model"
8 | "github.com/authorizerdev/authorizer/server/refs"
9 | "github.com/authorizerdev/authorizer/server/resolvers"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func resendVerifyEmailTests(t *testing.T, s TestSetup) {
14 | t.Helper()
15 | t.Run(`should resend verification email`, func(t *testing.T) {
16 | _, ctx := createContext(s)
17 | email := "resend_verify_email." + s.TestInfo.Email
18 | _, err := resolvers.SignupResolver(ctx, model.SignUpInput{
19 | Email: refs.NewStringRef(email),
20 | Password: s.TestInfo.Password,
21 | ConfirmPassword: s.TestInfo.Password,
22 | })
23 | assert.NoError(t, err)
24 | _, err = resolvers.ResendVerifyEmailResolver(ctx, model.ResendVerifyEmailInput{
25 | Email: email,
26 | Identifier: constants.VerificationTypeBasicAuthSignup,
27 | })
28 | assert.NoError(t, err)
29 |
30 | cleanData(email)
31 | })
32 | }
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 authorizer
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/server/resolvers/profile.go:
--------------------------------------------------------------------------------
1 | package resolvers
2 |
3 | import (
4 | "context"
5 |
6 | log "github.com/sirupsen/logrus"
7 |
8 | "github.com/authorizerdev/authorizer/server/db"
9 | "github.com/authorizerdev/authorizer/server/graph/model"
10 | "github.com/authorizerdev/authorizer/server/token"
11 | "github.com/authorizerdev/authorizer/server/utils"
12 | )
13 |
14 | // ProfileResolver is a resolver for profile query
15 | func ProfileResolver(ctx context.Context) (*model.User, error) {
16 | var res *model.User
17 |
18 | gc, err := utils.GinContextFromContext(ctx)
19 | if err != nil {
20 | log.Debug("Failed to get GinContext: ", err)
21 | return res, err
22 | }
23 | tokenData, err := token.GetUserIDFromSessionOrAccessToken(gc)
24 | if err != nil {
25 | log.Debug("Failed GetUserIDFromSessionOrAccessToken: ", err)
26 | return res, err
27 | }
28 | log := log.WithFields(log.Fields{
29 | "user_id": tokenData.UserID,
30 | })
31 | user, err := db.Provider.GetUserByID(ctx, tokenData.UserID)
32 | if err != nil {
33 | log.Debug("Failed to get user: ", err)
34 | return res, err
35 | }
36 |
37 | return user.AsAPIUser(), nil
38 | }
39 |
--------------------------------------------------------------------------------
/server/constants/verification_types.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | const (
4 | // VerificationTypeBasicAuthSignup is the basic_auth_signup verification type
5 | VerificationTypeBasicAuthSignup = "basic_auth_signup"
6 | // VerificationTypeMagicLinkLogin is the magic_link_login verification type
7 | VerificationTypeMagicLinkLogin = "magic_link_login"
8 | // VerificationTypeUpdateEmail is the update_email verification type
9 | VerificationTypeUpdateEmail = "update_email"
10 | // VerificationTypeForgotPassword is the forgot_password verification type
11 | VerificationTypeForgotPassword = "forgot_password"
12 | // VerificationTypeInviteMember is the invite_member verification type
13 | VerificationTypeInviteMember = "invite_member"
14 | // VerificationTypeOTP is the otp verification type
15 | VerificationTypeOTP = "verify_otp"
16 | )
17 |
18 | var (
19 | // VerificationTypes is slice of all verification types
20 | VerificationTypes = []string{
21 | VerificationTypeBasicAuthSignup,
22 | VerificationTypeMagicLinkLogin,
23 | VerificationTypeUpdateEmail,
24 | VerificationTypeForgotPassword,
25 | VerificationTypeInviteMember,
26 | }
27 | )
28 |
--------------------------------------------------------------------------------
/server/resolvers/email_templates.go:
--------------------------------------------------------------------------------
1 | package resolvers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/authorizerdev/authorizer/server/db"
8 | "github.com/authorizerdev/authorizer/server/graph/model"
9 | "github.com/authorizerdev/authorizer/server/token"
10 | "github.com/authorizerdev/authorizer/server/utils"
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | // EmailTemplatesResolver resolver for getting the list of email templates based on pagination
15 | func EmailTemplatesResolver(ctx context.Context, params *model.PaginatedInput) (*model.EmailTemplates, error) {
16 | gc, err := utils.GinContextFromContext(ctx)
17 | if err != nil {
18 | log.Debug("Failed to get GinContext: ", err)
19 | return nil, err
20 | }
21 |
22 | if !token.IsSuperAdmin(gc) {
23 | log.Debug("Not logged in as super admin")
24 | return nil, fmt.Errorf("unauthorized")
25 | }
26 |
27 | pagination := utils.GetPagination(params)
28 | emailTemplates, err := db.Provider.ListEmailTemplate(ctx, pagination)
29 | if err != nil {
30 | log.Debug("failed to get email templates: ", err)
31 | return nil, err
32 | }
33 | return emailTemplates, nil
34 | }
35 |
--------------------------------------------------------------------------------
/server/resolvers/verification_requests.go:
--------------------------------------------------------------------------------
1 | package resolvers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | log "github.com/sirupsen/logrus"
8 |
9 | "github.com/authorizerdev/authorizer/server/db"
10 | "github.com/authorizerdev/authorizer/server/graph/model"
11 | "github.com/authorizerdev/authorizer/server/token"
12 | "github.com/authorizerdev/authorizer/server/utils"
13 | )
14 |
15 | // VerificationRequestsResolver is a resolver for verification requests query
16 | // This is admin only query
17 | func VerificationRequestsResolver(ctx context.Context, params *model.PaginatedInput) (*model.VerificationRequests, error) {
18 | gc, err := utils.GinContextFromContext(ctx)
19 | if err != nil {
20 | log.Debug("Failed to get GinContext: ", err)
21 | return nil, err
22 | }
23 |
24 | if !token.IsSuperAdmin(gc) {
25 | log.Debug("Not logged in as super admin")
26 | return nil, fmt.Errorf("unauthorized")
27 | }
28 |
29 | pagination := utils.GetPagination(params)
30 | res, err := db.Provider.ListVerificationRequests(ctx, pagination)
31 | if err != nil {
32 | log.Debug("Failed to get verification requests: ", err)
33 | return nil, err
34 | }
35 |
36 | return res, nil
37 | }
38 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:1.25.5-alpine3.22 as go-builder
2 | WORKDIR /authorizer
3 | COPY server server
4 | COPY Makefile .
5 |
6 | ARG VERSION="latest"
7 | ENV VERSION="$VERSION"
8 |
9 | RUN echo "$VERSION"
10 | RUN apk add build-base &&\
11 | make clean && make && \
12 | chmod 777 build/server
13 |
14 | FROM node:24.12.0-alpine3.22 as node-builder
15 | WORKDIR /authorizer
16 | COPY app app
17 | COPY dashboard dashboard
18 | COPY Makefile .
19 | RUN apk add build-base &&\
20 | make build-app && \
21 | make build-dashboard
22 |
23 | FROM alpine:3.22
24 | RUN adduser -D -h /authorizer -u 1000 -k /dev/null authorizer
25 | WORKDIR /authorizer
26 | RUN mkdir app dashboard
27 | COPY --from=node-builder --chown=nobody:nobody /authorizer/app/build app/build
28 | COPY --from=node-builder --chown=nobody:nobody /authorizer/app/favicon_io app/favicon_io
29 | COPY --from=node-builder --chown=nobody:nobody /authorizer/dashboard/build dashboard/build
30 | COPY --from=node-builder --chown=nobody:nobody /authorizer/dashboard/favicon_io dashboard/favicon_io
31 | COPY --from=go-builder --chown=nobody:nobody /authorizer/build build
32 | COPY templates templates
33 | EXPOSE 8080
34 | USER authorizer
35 | CMD [ "./build/server" ]
36 |
--------------------------------------------------------------------------------
/server/db/models/authenticators.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | // Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
4 |
5 | // Authenticators model for db
6 | type Authenticator struct {
7 | Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
8 | ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
9 | UserID string `gorm:"type:char(36)" json:"user_id" bson:"user_id" cql:"user_id" dynamo:"user_id" index:"user_id,hash"`
10 | Method string `json:"method" bson:"method" cql:"method" dynamo:"method"`
11 | Secret string `json:"secret" bson:"secret" cql:"secret" dynamo:"secret"`
12 | RecoveryCodes *string `json:"recovery_codes" bson:"recovery_codes" cql:"recovery_codes" dynamo:"recovery_codes"`
13 | VerifiedAt *int64 `json:"verified_at" bson:"verified_at" cql:"verified_at" dynamo:"verified_at"`
14 | CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
15 | UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
16 | }
17 |
--------------------------------------------------------------------------------
/server/db/models/otp.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | const (
4 | // FieldName email is the field name for email
5 | FieldNameEmail = "email"
6 | // FieldNamePhoneNumber is the field name for phone number
7 | FieldNamePhoneNumber = "phone_number"
8 | )
9 |
10 | // OTP model for database
11 | type OTP struct {
12 | Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
13 | ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
14 | Email string `gorm:"index" json:"email" bson:"email" cql:"email" dynamo:"email" index:"email,hash"`
15 | PhoneNumber string `gorm:"index" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number"`
16 | Otp string `json:"otp" bson:"otp" cql:"otp" dynamo:"otp"`
17 | ExpiresAt int64 `json:"expires_at" bson:"expires_at" cql:"expires_at" dynamo:"expires_at"`
18 | CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
19 | UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
20 | }
21 |
22 | type Paging struct {
23 | ID string `json:"id,omitempty" dynamo:"id,hash"`
24 | }
25 |
--------------------------------------------------------------------------------
/dashboard/src/components/EnvComponents/DomainWhitelisting.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
3 | import InputField from '../../components/InputField';
4 | import { ArrayInputType } from '../../constants';
5 |
6 | const DomainWhiteListing = ({ variables, setVariables }: any) => {
7 | const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
8 | return (
9 |
10 | {' '}
11 |
12 | Domain White Listing
13 |
14 |
15 |
16 |
17 | Allowed Origins:
18 |
19 |
23 |
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
35 | export default DomainWhiteListing;
36 |
--------------------------------------------------------------------------------
/dashboard/src/utils/parseCSV.ts:
--------------------------------------------------------------------------------
1 | import _flatten from 'lodash/flatten';
2 | import { validateEmail } from '.';
3 |
4 | interface dataTypes {
5 | value: string;
6 | isInvalid: boolean;
7 | }
8 |
9 | const parseCSV = (file: File, delimiter: string): Promise => {
10 | return new Promise((resolve) => {
11 | const reader = new FileReader();
12 |
13 | // When the FileReader has loaded the file...
14 | reader.onload = (e: any) => {
15 | // Split the result to an array of lines
16 | const lines = e.target.result.split('\n');
17 | // Split the lines themselves by the specified
18 | // delimiter, such as a comma
19 | let result = lines.map((line: string) => line.split(delimiter));
20 | // As the FileReader reads asynchronously,
21 | // we can't just return the result; instead,
22 | // we're passing it to a callback function
23 | result = _flatten(result);
24 | resolve(
25 | result.map((email: string) => {
26 | return {
27 | value: email.trim(),
28 | isInvalid: !validateEmail(email.trim()),
29 | };
30 | }),
31 | );
32 | };
33 |
34 | // Read the file content as a single string
35 | reader.readAsText(file);
36 | });
37 | };
38 |
39 | export default parseCSV;
40 |
--------------------------------------------------------------------------------
/server/db/providers/provider_template/authenticator.go:
--------------------------------------------------------------------------------
1 | package provider_template
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/google/uuid"
8 |
9 | "github.com/authorizerdev/authorizer/server/db/models"
10 | )
11 |
12 | func (p *provider) AddAuthenticator(ctx context.Context, authenticators *models.Authenticator) (*models.Authenticator, error) {
13 | exists, _ := p.GetAuthenticatorDetailsByUserId(ctx, authenticators.UserID, authenticators.Method)
14 | if exists != nil {
15 | return authenticators, nil
16 | }
17 |
18 | if authenticators.ID == "" {
19 | authenticators.ID = uuid.New().String()
20 | }
21 | authenticators.CreatedAt = time.Now().Unix()
22 | authenticators.UpdatedAt = time.Now().Unix()
23 | return authenticators, nil
24 | }
25 |
26 | func (p *provider) UpdateAuthenticator(ctx context.Context, authenticators *models.Authenticator) (*models.Authenticator, error) {
27 | authenticators.UpdatedAt = time.Now().Unix()
28 | return authenticators, nil
29 | }
30 |
31 | func (p *provider) GetAuthenticatorDetailsByUserId(ctx context.Context, userId string, authenticatorType string) (*models.Authenticator, error) {
32 | var authenticators *models.Authenticator
33 | return authenticators, nil
34 | }
35 |
--------------------------------------------------------------------------------
/server/db/providers/sql/env.go:
--------------------------------------------------------------------------------
1 | package sql
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/authorizerdev/authorizer/server/db/models"
8 | "github.com/google/uuid"
9 | )
10 |
11 | // AddEnv to save environment information in database
12 | func (p *provider) AddEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
13 | if env.ID == "" {
14 | env.ID = uuid.New().String()
15 | }
16 |
17 | env.Key = env.ID
18 | env.CreatedAt = time.Now().Unix()
19 | env.UpdatedAt = time.Now().Unix()
20 |
21 | result := p.db.Create(&env)
22 | if result.Error != nil {
23 | return env, result.Error
24 | }
25 | return env, nil
26 | }
27 |
28 | // UpdateEnv to update environment information in database
29 | func (p *provider) UpdateEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
30 | env.UpdatedAt = time.Now().Unix()
31 | result := p.db.Save(&env)
32 | if result.Error != nil {
33 | return env, result.Error
34 | }
35 | return env, nil
36 | }
37 |
38 | // GetEnv to get environment information from database
39 | func (p *provider) GetEnv(ctx context.Context) (*models.Env, error) {
40 | var env *models.Env
41 | result := p.db.First(&env)
42 | if result.Error != nil {
43 | return env, result.Error
44 | }
45 | return env, nil
46 | }
47 |
--------------------------------------------------------------------------------
/server/resolvers/logout.go:
--------------------------------------------------------------------------------
1 | package resolvers
2 |
3 | import (
4 | "context"
5 |
6 | log "github.com/sirupsen/logrus"
7 |
8 | "github.com/authorizerdev/authorizer/server/cookie"
9 | "github.com/authorizerdev/authorizer/server/graph/model"
10 | "github.com/authorizerdev/authorizer/server/memorystore"
11 | "github.com/authorizerdev/authorizer/server/token"
12 | "github.com/authorizerdev/authorizer/server/utils"
13 | )
14 |
15 | // LogoutResolver is a resolver for logout mutation
16 | func LogoutResolver(ctx context.Context) (*model.Response, error) {
17 | gc, err := utils.GinContextFromContext(ctx)
18 | if err != nil {
19 | log.Debug("Failed to get GinContext: ", err)
20 | return nil, err
21 | }
22 |
23 | tokenData, err := token.GetUserIDFromSessionOrAccessToken(gc)
24 | if err != nil {
25 | log.Debug("Failed GetUserIDFromSessionOrAccessToken: ", err)
26 | return nil, err
27 | }
28 |
29 | sessionKey := tokenData.UserID
30 | if tokenData.LoginMethod != "" {
31 | sessionKey = tokenData.LoginMethod + ":" + tokenData.UserID
32 | }
33 |
34 | memorystore.Provider.DeleteUserSession(sessionKey, tokenData.Nonce)
35 | cookie.DeleteSession(gc)
36 |
37 | res := &model.Response{
38 | Message: "Logged out successfully",
39 | }
40 |
41 | return res, nil
42 | }
43 |
--------------------------------------------------------------------------------
/server/db/models/model.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | // CollectionList / Tables available for authorizer in the database
4 | type CollectionList struct {
5 | User string
6 | VerificationRequest string
7 | Session string
8 | Env string
9 | Webhook string
10 | WebhookLog string
11 | EmailTemplate string
12 | OTP string
13 | SMSVerificationRequest string
14 | Authenticators string
15 | }
16 |
17 | var (
18 | // Prefix for table name / collection names
19 | Prefix = "authorizer_"
20 | // Collections / Tables available for authorizer in the database (used for dbs other than gorm)
21 | Collections = CollectionList{
22 | User: Prefix + "users",
23 | VerificationRequest: Prefix + "verification_requests",
24 | Session: Prefix + "sessions",
25 | Env: Prefix + "env",
26 | Webhook: Prefix + "webhooks",
27 | WebhookLog: Prefix + "webhook_logs",
28 | EmailTemplate: Prefix + "email_templates",
29 | OTP: Prefix + "otps",
30 | SMSVerificationRequest: Prefix + "sms_verification_requests",
31 | Authenticators: Prefix + "authenticators",
32 | }
33 | )
34 |
--------------------------------------------------------------------------------
/server/test/webhooks_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/authorizerdev/authorizer/server/constants"
8 | "github.com/authorizerdev/authorizer/server/crypto"
9 | "github.com/authorizerdev/authorizer/server/graph/model"
10 | "github.com/authorizerdev/authorizer/server/memorystore"
11 | "github.com/authorizerdev/authorizer/server/refs"
12 | "github.com/authorizerdev/authorizer/server/resolvers"
13 | "github.com/stretchr/testify/assert"
14 | )
15 |
16 | func webhooksTest(t *testing.T, s TestSetup) {
17 | t.Helper()
18 | t.Run("should get webhooks", func(t *testing.T) {
19 | req, ctx := createContext(s)
20 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
21 | assert.NoError(t, err)
22 | h, err := crypto.EncryptPassword(adminSecret)
23 | assert.NoError(t, err)
24 | req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
25 |
26 | webhooks, err := resolvers.WebhooksResolver(ctx, &model.PaginatedInput{
27 | Pagination: &model.PaginationInput{
28 | Limit: refs.NewInt64Ref(20),
29 | },
30 | })
31 | assert.NoError(t, err)
32 | assert.NotEmpty(t, webhooks)
33 | assert.GreaterOrEqual(t, len(webhooks.Webhooks), len(s.TestInfo.TestWebhookEventTypes)*2)
34 | })
35 | }
36 |
--------------------------------------------------------------------------------
/dashboard/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dashboard",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js",
8 | "start": "NODE_ENV=development node ./esbuild.config.js",
9 | "format": "prettier --write --use-tabs 'src/**/*.(ts|tsx|js|jsx)'"
10 | },
11 | "keywords": [],
12 | "author": "Lakhan Samani",
13 | "license": "ISC",
14 | "dependencies": {
15 | "@chakra-ui/react": "^1.7.3",
16 | "@emotion/core": "^11.0.0",
17 | "@emotion/react": "^11.7.1",
18 | "@emotion/styled": "^11.6.0",
19 | "@types/react": "^17.0.38",
20 | "@types/react-dom": "^17.0.11",
21 | "@types/react-router-dom": "^5.3.2",
22 | "dayjs": "^1.10.7",
23 | "esbuild": "^0.14.9",
24 | "focus-visible": "^5.2.0",
25 | "framer-motion": "^5.5.5",
26 | "graphql": "^16.2.0",
27 | "lodash": "^4.17.21",
28 | "react": "^17.0.2",
29 | "react-dom": "^17.0.2",
30 | "react-draft-wysiwyg": "^1.15.0",
31 | "react-dropzone": "^12.0.4",
32 | "react-email-editor": "^1.6.1",
33 | "react-icons": "^4.3.1",
34 | "react-router-dom": "^6.2.1",
35 | "typescript": "^4.5.4",
36 | "urql": "^2.0.6"
37 | },
38 | "devDependencies": {
39 | "@types/react-email-editor": "^1.1.7",
40 | "prettier": "2.7.1"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/dashboard/src/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Fragment } from 'react';
3 | import { ChakraProvider, extendTheme } from '@chakra-ui/react';
4 | import { BrowserRouter } from 'react-router-dom';
5 | import { createClient, Provider } from 'urql';
6 | import { AppRoutes } from './routes';
7 | import { AuthContextProvider } from './contexts/AuthContext';
8 |
9 | const queryClient = createClient({
10 | url: '/graphql',
11 | fetchOptions: () => {
12 | return {
13 | credentials: 'include',
14 | headers: {
15 | 'x-authorizer-url': window.location.origin,
16 | },
17 | };
18 | },
19 | requestPolicy: 'network-only',
20 | });
21 |
22 | const theme = extendTheme({
23 | styles: {
24 | global: {
25 | 'html, body, #root': {
26 | height: '100%',
27 | outline: 'none',
28 | },
29 | },
30 | },
31 | colors: {
32 | blue: {
33 | 500: 'rgb(59,130,246)',
34 | },
35 | },
36 | });
37 |
38 | export default function App() {
39 | return (
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/server/memorystore/providers/inmemory/stores/env_store.go:
--------------------------------------------------------------------------------
1 | package stores
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | // EnvStore struct to store the env variables
8 | type EnvStore struct {
9 | mutex sync.Mutex
10 | store map[string]interface{}
11 | }
12 |
13 | // NewEnvStore create a new env store
14 | func NewEnvStore() *EnvStore {
15 | return &EnvStore{
16 | mutex: sync.Mutex{},
17 | store: make(map[string]interface{}),
18 | }
19 | }
20 |
21 | // UpdateEnvStore to update the whole env store object
22 | func (e *EnvStore) UpdateStore(store map[string]interface{}) {
23 | e.mutex.Lock()
24 | defer e.mutex.Unlock()
25 |
26 | // just override the keys + new keys
27 | for key, value := range store {
28 | e.store[key] = value
29 | }
30 | }
31 |
32 | // GetStore returns the env store
33 | func (e *EnvStore) GetStore() map[string]interface{} {
34 | e.mutex.Lock()
35 | defer e.mutex.Unlock()
36 | return e.store
37 | }
38 |
39 | // Get returns the value of the key in evn store
40 | func (e *EnvStore) Get(key string) interface{} {
41 | e.mutex.Lock()
42 | defer e.mutex.Unlock()
43 | return e.store[key]
44 | }
45 |
46 | // Set sets the value of the key in env store
47 | func (e *EnvStore) Set(key string, value interface{}) {
48 | e.mutex.Lock()
49 | defer e.mutex.Unlock()
50 |
51 | e.store[key] = value
52 | }
53 |
--------------------------------------------------------------------------------
/server/test/env_file_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 |
9 | "github.com/authorizerdev/authorizer/server/constants"
10 | "github.com/authorizerdev/authorizer/server/env"
11 | "github.com/authorizerdev/authorizer/server/memorystore"
12 | )
13 |
14 | func TestEnvs(t *testing.T) {
15 | err := os.Setenv(constants.EnvKeyEnvPath, "../../.env.test")
16 | assert.Nil(t, err)
17 | err = memorystore.InitRequiredEnv()
18 | assert.Nil(t, err)
19 | err = env.InitAllEnv()
20 | assert.Nil(t, err)
21 | store, err := memorystore.Provider.GetEnvStore()
22 | assert.Nil(t, err)
23 |
24 | assert.Equal(t, "test", store[constants.EnvKeyEnv].(string))
25 | assert.False(t, store[constants.EnvKeyDisableEmailVerification].(bool))
26 | assert.False(t, store[constants.EnvKeyDisableMagicLinkLogin].(bool))
27 | assert.False(t, store[constants.EnvKeyDisableBasicAuthentication].(bool))
28 | assert.Equal(t, "RS256", store[constants.EnvKeyJwtType].(string))
29 | assert.Equal(t, store[constants.EnvKeyJwtRoleClaim].(string), "role")
30 | assert.EqualValues(t, store[constants.EnvKeyRoles].(string), "user")
31 | assert.EqualValues(t, store[constants.EnvKeyDefaultRoles].(string), "user")
32 | assert.EqualValues(t, store[constants.EnvKeyAllowedOrigins].(string), "*")
33 | }
34 |
--------------------------------------------------------------------------------
/server/test/test_endpoint_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/authorizerdev/authorizer/server/constants"
8 | "github.com/authorizerdev/authorizer/server/crypto"
9 | "github.com/authorizerdev/authorizer/server/graph/model"
10 | "github.com/authorizerdev/authorizer/server/memorystore"
11 | "github.com/authorizerdev/authorizer/server/resolvers"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | func testEndpointTest(t *testing.T, s TestSetup) {
16 | t.Helper()
17 | t.Run("should test endpoint", func(t *testing.T) {
18 | req, ctx := createContext(s)
19 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
20 | assert.NoError(t, err)
21 | h, err := crypto.EncryptPassword(adminSecret)
22 | assert.NoError(t, err)
23 | req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
24 |
25 | res, err := resolvers.TestEndpointResolver(ctx, model.TestEndpointRequest{
26 | Endpoint: s.TestInfo.WebhookEndpoint,
27 | EventName: constants.UserLoginWebhookEvent,
28 | Headers: map[string]interface{}{
29 | "x-test": "test",
30 | },
31 | })
32 | assert.NoError(t, err)
33 | assert.NotNil(t, res)
34 | assert.GreaterOrEqual(t, *res.HTTPStatus, int64(200))
35 | assert.NotEmpty(t, res.Response)
36 | })
37 | }
38 |
--------------------------------------------------------------------------------
/server/constants/db_types.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | const (
4 | // DbTypePostgres is the postgres database type
5 | DbTypePostgres = "postgres"
6 | // DbTypeSqlite is the sqlite database type
7 | DbTypeSqlite = "sqlite"
8 | // DbTypeLibSQL is the libsql / Turso database type
9 | DbTypeLibSQL = "libsql"
10 | // DbTypeMysql is the mysql database type
11 | DbTypeMysql = "mysql"
12 | // DbTypeSqlserver is the sqlserver database type
13 | DbTypeSqlserver = "sqlserver"
14 | // DbTypeArangodb is the arangodb database type
15 | DbTypeArangodb = "arangodb"
16 | // DbTypeMongodb is the mongodb database type
17 | DbTypeMongodb = "mongodb"
18 | // DbTypeYugabyte is the yugabyte database type
19 | DbTypeYugabyte = "yugabyte"
20 | // DbTypeMariaDB is the mariadb database type
21 | DbTypeMariaDB = "mariadb"
22 | // DbTypeCassandra is the cassandra database type
23 | DbTypeCassandraDB = "cassandradb"
24 | // DbTypeScyllaDB is the scylla database type
25 | DbTypeScyllaDB = "scylladb"
26 | // DbTypeCockroachDB is the cockroach database type
27 | DbTypeCockroachDB = "cockroachdb"
28 | // DbTypePlanetScaleDB is the planetscale database type
29 | DbTypePlanetScaleDB = "planetscale"
30 | // DbTypeDynamoDB is the Dynamo database type
31 | DbTypeDynamoDB = "dynamodb"
32 | // DbTypeCouchbaseDB is the Couchbase database type
33 | DbTypeCouchbaseDB = "couchbase"
34 | )
35 |
--------------------------------------------------------------------------------
/dashboard/src/contexts/AuthContext.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useState, useContext, useEffect } from 'react';
2 | import { Center, Spinner } from '@chakra-ui/react';
3 | import { useQuery } from 'urql';
4 | import { useLocation, useNavigate } from 'react-router-dom';
5 |
6 | import { AdminSessionQuery } from '../graphql/queries';
7 | import { hasAdminSecret } from '../utils';
8 |
9 | const AuthContext = createContext({
10 | isLoggedIn: false,
11 | setIsLoggedIn: (data: boolean) => {},
12 | });
13 |
14 | export const AuthContextProvider = ({ children }: { children: any }) => {
15 | const [isLoggedIn, setIsLoggedIn] = useState(false);
16 |
17 | const { pathname } = useLocation();
18 | const navigate = useNavigate();
19 |
20 | const [{ fetching, data, error }] = useQuery({
21 | query: AdminSessionQuery,
22 | });
23 |
24 | useEffect(() => {
25 | if (!fetching && !error) {
26 | setIsLoggedIn(true);
27 | if (pathname === '/login' || pathname === 'signup') {
28 | navigate('/', { replace: true });
29 | }
30 | }
31 | }, [fetching, error]);
32 |
33 | if (fetching) {
34 | return (
35 |
36 |
37 |
38 | );
39 | }
40 |
41 | return (
42 |
43 | {children}
44 |
45 | );
46 | };
47 |
48 | export const useAuthContext = () => useContext(AuthContext);
49 |
--------------------------------------------------------------------------------
/dashboard/src/layouts/AuthLayout.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Flex,
4 | Image,
5 | Text,
6 | Spinner,
7 | useMediaQuery,
8 | } from '@chakra-ui/react';
9 | import React from 'react';
10 | import { useQuery } from 'urql';
11 | import { MetaQuery } from '../graphql/queries';
12 |
13 | export function AuthLayout({ children }: { children: React.ReactNode }) {
14 | const [{ fetching, data }] = useQuery({ query: MetaQuery });
15 | const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
16 | return (
17 |
25 |
26 |
31 |
32 | AUTHORIZER
33 |
34 |
35 |
36 | {fetching ? (
37 |
38 | ) : (
39 | <>
40 |
49 | {children}
50 |
51 |
52 | Current Version: {data.meta.version}
53 |
54 | >
55 | )}
56 |
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/server/test/forgot_password_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/authorizerdev/authorizer/server/constants"
7 | "github.com/authorizerdev/authorizer/server/db"
8 | "github.com/authorizerdev/authorizer/server/graph/model"
9 | "github.com/authorizerdev/authorizer/server/refs"
10 | "github.com/authorizerdev/authorizer/server/resolvers"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | func forgotPasswordTest(t *testing.T, s TestSetup) {
15 | t.Helper()
16 | t.Run(`should run forgot password`, func(t *testing.T) {
17 | _, ctx := createContext(s)
18 | email := "forgot_password." + s.TestInfo.Email
19 | res, err := resolvers.SignupResolver(ctx, model.SignUpInput{
20 | Email: refs.NewStringRef(email),
21 | Password: s.TestInfo.Password,
22 | ConfirmPassword: s.TestInfo.Password,
23 | })
24 | assert.NoError(t, err)
25 | assert.NotNil(t, res)
26 | forgotPasswordRes, err := resolvers.ForgotPasswordResolver(ctx, model.ForgotPasswordInput{
27 | Email: refs.NewStringRef(email),
28 | })
29 | assert.Nil(t, err, "no errors for forgot password")
30 | assert.NotNil(t, forgotPasswordRes)
31 | verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeForgotPassword)
32 | assert.Nil(t, err)
33 |
34 | assert.Equal(t, verificationRequest.Identifier, constants.VerificationTypeForgotPassword)
35 |
36 | cleanData(email)
37 | })
38 | }
39 |
--------------------------------------------------------------------------------
/dashboard/src/components/EnvComponents/SessionStorage.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
3 | import InputField from '../InputField';
4 |
5 | const SessionStorage = ({ variables, setVariables, RedisURL }: any) => {
6 | const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
7 | return (
8 |
9 | {' '}
10 |
11 | Session Storage
12 |
13 |
14 | Note: Redis related environment variables cannot be updated from
15 | dashboard. Please use .env file or OS environment variables to update
16 | it.
17 |
18 |
19 |
20 |
21 | Redis URL:
22 |
23 |
27 |
35 |
36 |
37 |
38 |
39 | );
40 | };
41 |
42 | export default SessionStorage;
43 |
--------------------------------------------------------------------------------
/server/resolvers/delete_webhook.go:
--------------------------------------------------------------------------------
1 | package resolvers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/authorizerdev/authorizer/server/db"
8 | "github.com/authorizerdev/authorizer/server/graph/model"
9 | "github.com/authorizerdev/authorizer/server/token"
10 | "github.com/authorizerdev/authorizer/server/utils"
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | // DeleteWebhookResolver resolver to delete webhook and its relevant logs
15 | func DeleteWebhookResolver(ctx context.Context, params model.WebhookRequest) (*model.Response, error) {
16 | gc, err := utils.GinContextFromContext(ctx)
17 | if err != nil {
18 | log.Debug("Failed to get GinContext: ", err)
19 | return nil, err
20 | }
21 |
22 | if !token.IsSuperAdmin(gc) {
23 | log.Debug("Not logged in as super admin")
24 | return nil, fmt.Errorf("unauthorized")
25 | }
26 |
27 | if params.ID == "" {
28 | log.Debug("webhookID is required")
29 | return nil, fmt.Errorf("webhook ID required")
30 | }
31 |
32 | log := log.WithField("webhook_id", params.ID)
33 |
34 | webhook, err := db.Provider.GetWebhookByID(ctx, params.ID)
35 | if err != nil {
36 | log.Debug("failed to get webhook: ", err)
37 | return nil, err
38 | }
39 |
40 | err = db.Provider.DeleteWebhook(ctx, webhook)
41 | if err != nil {
42 | log.Debug("failed to delete webhook: ", err)
43 | return nil, err
44 | }
45 |
46 | return &model.Response{
47 | Message: "Webhook deleted successfully",
48 | }, nil
49 | }
50 |
--------------------------------------------------------------------------------
/scripts/couchbase-test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -x
4 | set -m
5 |
6 | sleep 15
7 |
8 | # Setup index and memory quota
9 | # curl -v -X POST http://127.0.0.1:8091/pools/default -d memoryQuota=300 -d indexMemoryQuota=300
10 |
11 | # Setup services
12 | curl -v http://127.0.0.1:8091/node/controller/setupServices -d services=kv%2Cn1ql%2Cindex
13 |
14 | # Setup credentials
15 | curl -v http://127.0.0.1:8091/settings/web -d port=8091 -d username=Administrator -d password=password
16 |
17 | # Setup Memory Optimized Indexes
18 | curl -i -u Administrator:password -X POST http://127.0.0.1:8091/settings/indexes -d 'storageMode=memory_optimized'
19 |
20 | # Load travel-sample bucket
21 | #curl -v -u Administrator:password -X POST http://127.0.0.1:8091/sampleBuckets/install -d '["travel-sample"]'
22 |
23 | echo "Type: $TYPE"
24 |
25 | if [ "$TYPE" = "WORKER" ]; then
26 | echo "Sleeping ..."
27 | sleep 15
28 |
29 | #IP=`hostname -s`
30 | IP=`hostname -I | cut -d ' ' -f1`
31 | echo "IP: " $IP
32 |
33 | echo "Auto Rebalance: $AUTO_REBALANCE"
34 | if [ "$AUTO_REBALANCE" = "true" ]; then
35 | couchbase-cli rebalance --cluster=$COUCHBASE_MASTER:8091 --user=Administrator --password=password --server-add=$IP --server-add-username=Administrator --server-add-password=password
36 | else
37 | couchbase-cli server-add --cluster=$COUCHBASE_MASTER:8091 --user=Administrator --password=password --server-add=$IP --server-add-username=Administrator --server-add-password=password
38 | fi;
39 | fi;
--------------------------------------------------------------------------------
/server/resolvers/admin_login.go:
--------------------------------------------------------------------------------
1 | package resolvers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | log "github.com/sirupsen/logrus"
8 |
9 | "github.com/authorizerdev/authorizer/server/constants"
10 | "github.com/authorizerdev/authorizer/server/cookie"
11 | "github.com/authorizerdev/authorizer/server/crypto"
12 | "github.com/authorizerdev/authorizer/server/graph/model"
13 | "github.com/authorizerdev/authorizer/server/memorystore"
14 | "github.com/authorizerdev/authorizer/server/utils"
15 | )
16 |
17 | // AdminLoginResolver is a resolver for admin login mutation
18 | func AdminLoginResolver(ctx context.Context, params model.AdminLoginInput) (*model.Response, error) {
19 | var res *model.Response
20 |
21 | gc, err := utils.GinContextFromContext(ctx)
22 | if err != nil {
23 | log.Debug("Failed to get GinContext: ", err)
24 | return res, err
25 | }
26 |
27 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
28 | if err != nil {
29 | log.Debug("Error getting admin secret: ", err)
30 | return res, err
31 | }
32 | if params.AdminSecret != adminSecret {
33 | log.Debug("Admin secret is not correct")
34 | return res, fmt.Errorf(`invalid admin secret`)
35 | }
36 |
37 | hashedKey, err := crypto.EncryptPassword(adminSecret)
38 | if err != nil {
39 | return res, err
40 | }
41 | cookie.SetAdminCookie(gc, hashedKey)
42 |
43 | res = &model.Response{
44 | Message: "admin logged in successfully",
45 | }
46 | return res, nil
47 | }
48 |
--------------------------------------------------------------------------------
/server/logs/logs.go:
--------------------------------------------------------------------------------
1 | package logs
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/sirupsen/logrus"
7 | log "github.com/sirupsen/logrus"
8 | )
9 |
10 | // LogUTCFormatter hels in setting UTC time format for the logs
11 | type LogUTCFormatter struct {
12 | log.Formatter
13 | }
14 |
15 | // Format helps fomratting time to UTC
16 | func (u LogUTCFormatter) Format(e *log.Entry) ([]byte, error) {
17 | e.Time = e.Time.UTC()
18 | return u.Formatter.Format(e)
19 | }
20 |
21 | func InitLog(cliLogLevel string) *log.Logger {
22 |
23 | // log instance for gin server
24 | log := logrus.New()
25 | log.SetFormatter(LogUTCFormatter{&logrus.JSONFormatter{}})
26 |
27 | if cliLogLevel == "" {
28 | cliLogLevel = os.Getenv("LOG_LEVEL")
29 | }
30 |
31 | var logLevel logrus.Level
32 | switch cliLogLevel {
33 | case "debug":
34 | logLevel = logrus.DebugLevel
35 | case "info":
36 | logLevel = logrus.InfoLevel
37 | case "warn":
38 | logLevel = logrus.WarnLevel
39 | case "error":
40 | logLevel = logrus.ErrorLevel
41 | case "fatal":
42 | logLevel = logrus.FatalLevel
43 | case "panic":
44 | logLevel = logrus.PanicLevel
45 | default:
46 | logLevel = logrus.InfoLevel
47 | }
48 | // set log level globally
49 | logrus.SetLevel(logLevel)
50 |
51 | // set log level for go-gin middleware
52 | log.SetLevel(logLevel)
53 |
54 | // show file path in log for debug or other log levels.
55 | if logLevel != logrus.InfoLevel {
56 | logrus.SetReportCaller(true)
57 | log.SetReportCaller(true)
58 | }
59 |
60 | return log
61 | }
62 |
--------------------------------------------------------------------------------
/server/handlers/playground.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/99designs/gqlgen/graphql/playground"
7 | "github.com/gin-gonic/gin"
8 |
9 | log "github.com/sirupsen/logrus"
10 |
11 | "github.com/authorizerdev/authorizer/server/constants"
12 | "github.com/authorizerdev/authorizer/server/memorystore"
13 | "github.com/authorizerdev/authorizer/server/token"
14 | )
15 |
16 | // PlaygroundHandler is the handler for the /playground route
17 | func PlaygroundHandler() gin.HandlerFunc {
18 | return func(c *gin.Context) {
19 | var h http.HandlerFunc
20 |
21 | disablePlayground, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisablePlayGround)
22 | if err != nil {
23 | log.Debug("error while getting disable playground value")
24 | disablePlayground = false
25 | }
26 |
27 | // if env set to false, then check if logged in as super admin, if logged in then return graphql else 401 error
28 | // if env set to true, then disabled the playground with 404 error
29 | if !disablePlayground {
30 | if token.IsSuperAdmin(c) {
31 | h = playground.Handler("GraphQL", "/graphql")
32 | } else {
33 | log.Debug("not logged in as super admin")
34 | c.JSON(http.StatusUnauthorized, gin.H{"error": "not logged in as super admin"})
35 | return
36 | }
37 | } else {
38 | log.Debug("playground is disabled")
39 | c.JSON(http.StatusNotFound, gin.H{"error": "playground is disabled"})
40 | return
41 | }
42 | h.ServeHTTP(c.Writer, c.Request)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/server/resolvers/webhook_logs.go:
--------------------------------------------------------------------------------
1 | package resolvers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/authorizerdev/authorizer/server/db"
8 | "github.com/authorizerdev/authorizer/server/graph/model"
9 | "github.com/authorizerdev/authorizer/server/refs"
10 | "github.com/authorizerdev/authorizer/server/token"
11 | "github.com/authorizerdev/authorizer/server/utils"
12 | log "github.com/sirupsen/logrus"
13 | )
14 |
15 | // WebhookLogsResolver resolver for getting the list of webhook_logs based on pagination & webhook identifier
16 | func WebhookLogsResolver(ctx context.Context, params *model.ListWebhookLogRequest) (*model.WebhookLogs, error) {
17 | gc, err := utils.GinContextFromContext(ctx)
18 | if err != nil {
19 | log.Debug("Failed to get GinContext: ", err)
20 | return nil, err
21 | }
22 |
23 | if !token.IsSuperAdmin(gc) {
24 | log.Debug("Not logged in as super admin")
25 | return nil, fmt.Errorf("unauthorized")
26 | }
27 |
28 | var pagination *model.Pagination
29 | var webhookID string
30 |
31 | if params != nil {
32 | pagination = utils.GetPagination(&model.PaginatedInput{
33 | Pagination: params.Pagination,
34 | })
35 | webhookID = refs.StringValue(params.WebhookID)
36 | } else {
37 | pagination = utils.GetPagination(nil)
38 | webhookID = ""
39 | }
40 | // TODO fix
41 | webhookLogs, err := db.Provider.ListWebhookLogs(ctx, pagination, webhookID)
42 | if err != nil {
43 | log.Debug("failed to get webhook logs: ", err)
44 | return nil, err
45 | }
46 | return webhookLogs, nil
47 | }
48 |
--------------------------------------------------------------------------------
/server/test/delete_user_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/authorizerdev/authorizer/server/constants"
8 | "github.com/authorizerdev/authorizer/server/crypto"
9 | "github.com/authorizerdev/authorizer/server/graph/model"
10 | "github.com/authorizerdev/authorizer/server/memorystore"
11 | "github.com/authorizerdev/authorizer/server/refs"
12 | "github.com/authorizerdev/authorizer/server/resolvers"
13 | "github.com/stretchr/testify/assert"
14 | )
15 |
16 | func deleteUserTest(t *testing.T, s TestSetup) {
17 | t.Helper()
18 | t.Run(`should delete users with admin secret only`, func(t *testing.T) {
19 | req, ctx := createContext(s)
20 | email := "delete_user." + s.TestInfo.Email
21 | resolvers.SignupResolver(ctx, model.SignUpInput{
22 | Email: refs.NewStringRef(email),
23 | Password: s.TestInfo.Password,
24 | ConfirmPassword: s.TestInfo.Password,
25 | })
26 |
27 | _, err := resolvers.DeleteUserResolver(ctx, model.DeleteUserInput{
28 | Email: email,
29 | })
30 | assert.NotNil(t, err, "unauthorized")
31 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
32 | assert.Nil(t, err)
33 |
34 | h, err := crypto.EncryptPassword(adminSecret)
35 | assert.Nil(t, err)
36 | req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
37 |
38 | _, err = resolvers.DeleteUserResolver(ctx, model.DeleteUserInput{
39 | Email: email,
40 | })
41 | assert.Nil(t, err)
42 | cleanData(email)
43 | })
44 | }
45 |
--------------------------------------------------------------------------------
/server/db/providers/couchbase/shared.go:
--------------------------------------------------------------------------------
1 | package couchbase
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "reflect"
7 | "strings"
8 |
9 | "github.com/couchbase/gocb/v2"
10 | )
11 |
12 | func GetSetFields(webhookMap map[string]interface{}) (string, map[string]interface{}) {
13 | params := make(map[string]interface{}, 1)
14 | updateFields := ""
15 | for key, value := range webhookMap {
16 | if key == "_id" {
17 | continue
18 | }
19 | if key == "_key" {
20 | continue
21 | }
22 | if value == nil {
23 | updateFields += fmt.Sprintf("%s=$%s,", key, key)
24 | params[key] = "null"
25 | continue
26 | }
27 | valueType := reflect.TypeOf(value)
28 | if valueType.Name() == "string" {
29 | updateFields += fmt.Sprintf("%s = $%s, ", key, key)
30 | params[key] = value.(string)
31 |
32 | } else {
33 | updateFields += fmt.Sprintf("%s = $%s, ", key, key)
34 | params[key] = value
35 | }
36 | }
37 | updateFields = strings.Trim(updateFields, " ")
38 | updateFields = strings.TrimSuffix(updateFields, ",")
39 | return updateFields, params
40 | }
41 |
42 | func (p *provider) GetTotalDocs(ctx context.Context, collection string) (int64, error) {
43 | totalDocs := TotalDocs{}
44 | countQuery := fmt.Sprintf("SELECT COUNT(*) as Total FROM %s.%s", p.scopeName, collection)
45 | queryRes, err := p.db.Query(countQuery, &gocb.QueryOptions{
46 | Context: ctx,
47 | })
48 | queryRes.One(&totalDocs)
49 | if err != nil {
50 | return 0, err
51 | }
52 | return totalDocs.Total, nil
53 | }
54 |
55 | type TotalDocs struct {
56 | Total int64
57 | }
58 |
--------------------------------------------------------------------------------
/server/resolvers/delete_email_template.go:
--------------------------------------------------------------------------------
1 | package resolvers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/authorizerdev/authorizer/server/db"
8 | "github.com/authorizerdev/authorizer/server/graph/model"
9 | "github.com/authorizerdev/authorizer/server/token"
10 | "github.com/authorizerdev/authorizer/server/utils"
11 | log "github.com/sirupsen/logrus"
12 | )
13 |
14 | // DeleteEmailTemplateResolver resolver to delete email template and its relevant logs
15 | func DeleteEmailTemplateResolver(ctx context.Context, params model.DeleteEmailTemplateRequest) (*model.Response, error) {
16 | gc, err := utils.GinContextFromContext(ctx)
17 | if err != nil {
18 | log.Debug("Failed to get GinContext: ", err)
19 | return nil, err
20 | }
21 |
22 | if !token.IsSuperAdmin(gc) {
23 | log.Debug("Not logged in as super admin")
24 | return nil, fmt.Errorf("unauthorized")
25 | }
26 |
27 | if params.ID == "" {
28 | log.Debug("email template is required")
29 | return nil, fmt.Errorf("email template ID required")
30 | }
31 |
32 | log := log.WithField("email_template_id", params.ID)
33 |
34 | emailTemplate, err := db.Provider.GetEmailTemplateByID(ctx, params.ID)
35 | if err != nil {
36 | log.Debug("failed to get email template: ", err)
37 | return nil, err
38 | }
39 |
40 | err = db.Provider.DeleteEmailTemplate(ctx, emailTemplate)
41 | if err != nil {
42 | log.Debug("failed to delete email template: ", err)
43 | return nil, err
44 | }
45 |
46 | return &model.Response{
47 | Message: "Email templated deleted successfully",
48 | }, nil
49 | }
50 |
--------------------------------------------------------------------------------
/server/resolvers/admin_session.go:
--------------------------------------------------------------------------------
1 | package resolvers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | log "github.com/sirupsen/logrus"
8 |
9 | "github.com/authorizerdev/authorizer/server/constants"
10 | "github.com/authorizerdev/authorizer/server/cookie"
11 | "github.com/authorizerdev/authorizer/server/crypto"
12 | "github.com/authorizerdev/authorizer/server/graph/model"
13 | "github.com/authorizerdev/authorizer/server/memorystore"
14 | "github.com/authorizerdev/authorizer/server/token"
15 | "github.com/authorizerdev/authorizer/server/utils"
16 | )
17 |
18 | // AdminSessionResolver is a resolver for admin session query
19 | func AdminSessionResolver(ctx context.Context) (*model.Response, error) {
20 | var res *model.Response
21 |
22 | gc, err := utils.GinContextFromContext(ctx)
23 | if err != nil {
24 | log.Debug("Failed to get GinContext: ", err)
25 | return res, err
26 | }
27 |
28 | if !token.IsSuperAdmin(gc) {
29 | log.Debug("Not logged in as super admin")
30 | return res, fmt.Errorf("unauthorized")
31 | }
32 |
33 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
34 | if err != nil {
35 | log.Debug("Error getting admin secret: ", err)
36 | return res, fmt.Errorf("unauthorized")
37 | }
38 | hashedKey, err := crypto.EncryptPassword(adminSecret)
39 | if err != nil {
40 | log.Debug("Failed to encrypt key: ", err)
41 | return res, err
42 | }
43 | cookie.SetAdminCookie(gc, hashedKey)
44 |
45 | res = &model.Response{
46 | Message: "admin logged in successfully",
47 | }
48 | return res, nil
49 | }
50 |
--------------------------------------------------------------------------------
/server/constants/auth_methods.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | const (
4 | // AuthRecipeMethodBasicAuth is the basic_auth auth method
5 | AuthRecipeMethodBasicAuth = "basic_auth"
6 | // AuthRecipeMethodMobileBasicAuth is the mobile basic_auth method, where user can signup using mobile number and password
7 | AuthRecipeMethodMobileBasicAuth = "mobile_basic_auth"
8 | // AuthRecipeMethodMagicLinkLogin is the magic_link_login auth method
9 | AuthRecipeMethodMagicLinkLogin = "magic_link_login"
10 | // AuthRecipeMethodMobileOTP is the mobile_otp auth method
11 | AuthRecipeMethodMobileOTP = "mobile_otp"
12 | // AuthRecipeMethodGoogle is the google auth method
13 | AuthRecipeMethodGoogle = "google"
14 | // AuthRecipeMethodGithub is the github auth method
15 | AuthRecipeMethodGithub = "github"
16 | // AuthRecipeMethodFacebook is the facebook auth method
17 | AuthRecipeMethodFacebook = "facebook"
18 | // AuthRecipeMethodLinkedin is the linkedin auth method
19 | AuthRecipeMethodLinkedIn = "linkedin"
20 | // AuthRecipeMethodApple is the apple auth method
21 | AuthRecipeMethodApple = "apple"
22 | // AuthRecipeMethodDiscord is the discord auth method
23 | AuthRecipeMethodDiscord = "discord"
24 | // AuthRecipeMethodTwitter is the twitter auth method
25 | AuthRecipeMethodTwitter = "twitter"
26 | // AuthRecipeMethodMicrosoft is the microsoft auth method
27 | AuthRecipeMethodMicrosoft = "microsoft"
28 | // AuthRecipeMethodTwitch is the twitch auth method
29 | AuthRecipeMethodTwitch = "twitch"
30 | // AuthRecipeMethodRoblox is the roblox auth method
31 | AuthRecipeMethodRoblox = "roblox"
32 | )
33 |
--------------------------------------------------------------------------------
/server/validators/url.go:
--------------------------------------------------------------------------------
1 | package validators
2 |
3 | import (
4 | "regexp"
5 | "strings"
6 |
7 | "github.com/authorizerdev/authorizer/server/constants"
8 | "github.com/authorizerdev/authorizer/server/memorystore"
9 | "github.com/authorizerdev/authorizer/server/parsers"
10 | )
11 |
12 | // IsValidOrigin validates origin based on ALLOWED_ORIGINS
13 | func IsValidOrigin(url string) bool {
14 | allowedOriginsString, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAllowedOrigins)
15 | allowedOrigins := []string{}
16 | if err != nil {
17 | allowedOrigins = []string{"*"}
18 | } else {
19 | allowedOrigins = strings.Split(allowedOriginsString, ",")
20 | }
21 | if len(allowedOrigins) == 1 && allowedOrigins[0] == "*" {
22 | return true
23 | }
24 |
25 | hasValidURL := false
26 | hostName, port := parsers.GetHostParts(url)
27 | currentOrigin := hostName + ":" + port
28 |
29 | for _, origin := range allowedOrigins {
30 | replacedString := origin
31 | // if has regex whitelisted domains
32 | if strings.Contains(origin, "*") {
33 | replacedString = strings.ReplaceAll(origin, ".", "\\.")
34 | replacedString = strings.ReplaceAll(replacedString, "*", ".*")
35 |
36 | if strings.HasPrefix(replacedString, ".*") {
37 | replacedString += "\\b"
38 | }
39 |
40 | if strings.HasSuffix(replacedString, ".*") {
41 | replacedString = "\\b" + replacedString
42 | }
43 | }
44 |
45 | if matched, _ := regexp.MatchString(replacedString, currentOrigin); matched {
46 | hasValidURL = true
47 | break
48 | }
49 | }
50 |
51 | return hasValidURL
52 | }
53 |
--------------------------------------------------------------------------------
/server/resolvers/user.go:
--------------------------------------------------------------------------------
1 | package resolvers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "strings"
7 |
8 | log "github.com/sirupsen/logrus"
9 |
10 | "github.com/authorizerdev/authorizer/server/db"
11 | "github.com/authorizerdev/authorizer/server/graph/model"
12 | "github.com/authorizerdev/authorizer/server/token"
13 | "github.com/authorizerdev/authorizer/server/utils"
14 | )
15 |
16 | // UserResolver is a resolver for user query
17 | // This is admin only query
18 | func UserResolver(ctx context.Context, params model.GetUserRequest) (*model.User, error) {
19 | gc, err := utils.GinContextFromContext(ctx)
20 | if err != nil {
21 | log.Debug("Failed to get GinContext: ", err)
22 | return nil, err
23 | }
24 | if !token.IsSuperAdmin(gc) {
25 | log.Debug("Not logged in as super admin.")
26 | return nil, fmt.Errorf("unauthorized")
27 | }
28 | // Try getting user by ID
29 | if params.ID != nil && strings.Trim(*params.ID, " ") != "" {
30 | res, err := db.Provider.GetUserByID(ctx, *params.ID)
31 | if err != nil {
32 | log.Debug("Failed to get users by ID: ", err)
33 | return nil, err
34 | }
35 | return res.AsAPIUser(), nil
36 | }
37 | // Try getting user by email
38 | if params.Email != nil && strings.Trim(*params.Email, " ") != "" {
39 | res, err := db.Provider.GetUserByEmail(ctx, *params.Email)
40 | if err != nil {
41 | log.Debug("Failed to get users by email: ", err)
42 | return nil, err
43 | }
44 | return res.AsAPIUser(), nil
45 | }
46 | // Return error if no params are provided
47 | return nil, fmt.Errorf("invalid params, user id or email is required")
48 | }
49 |
--------------------------------------------------------------------------------
/server/resolvers/enable_access.go:
--------------------------------------------------------------------------------
1 | package resolvers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | log "github.com/sirupsen/logrus"
8 |
9 | "github.com/authorizerdev/authorizer/server/constants"
10 | "github.com/authorizerdev/authorizer/server/db"
11 | "github.com/authorizerdev/authorizer/server/graph/model"
12 | "github.com/authorizerdev/authorizer/server/token"
13 | "github.com/authorizerdev/authorizer/server/utils"
14 | )
15 |
16 | // EnableAccessResolver is a resolver for enabling user access
17 | func EnableAccessResolver(ctx context.Context, params model.UpdateAccessInput) (*model.Response, error) {
18 | var res *model.Response
19 |
20 | gc, err := utils.GinContextFromContext(ctx)
21 | if err != nil {
22 | log.Debug("Failed to get GinContext: ", err)
23 | return res, err
24 | }
25 |
26 | if !token.IsSuperAdmin(gc) {
27 | log.Debug("Not logged in as super admin.")
28 | return res, fmt.Errorf("unauthorized")
29 | }
30 |
31 | log := log.WithFields(log.Fields{
32 | "user_id": params.UserID,
33 | })
34 |
35 | user, err := db.Provider.GetUserByID(ctx, params.UserID)
36 | if err != nil {
37 | log.Debug("Failed to get user from DB: ", err)
38 | return res, err
39 | }
40 |
41 | user.RevokedTimestamp = nil
42 |
43 | user, err = db.Provider.UpdateUser(ctx, user)
44 | if err != nil {
45 | log.Debug("Failed to update user: ", err)
46 | return res, err
47 | }
48 |
49 | res = &model.Response{
50 | Message: `user access enabled successfully`,
51 | }
52 |
53 | go utils.RegisterEvent(ctx, constants.UserAccessEnabledWebhookEvent, "", user)
54 |
55 | return res, nil
56 | }
57 |
--------------------------------------------------------------------------------
/server/db/models/email_templates.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/authorizerdev/authorizer/server/graph/model"
7 | "github.com/authorizerdev/authorizer/server/refs"
8 | )
9 |
10 | // EmailTemplate model for database
11 | type EmailTemplate struct {
12 | Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
13 | ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
14 | EventName string `gorm:"unique" json:"event_name" bson:"event_name" cql:"event_name" dynamo:"event_name" index:"event_name,hash"`
15 | Subject string `json:"subject" bson:"subject" cql:"subject" dynamo:"subject"`
16 | Template string `json:"template" bson:"template" cql:"template" dynamo:"template"`
17 | Design string `json:"design" bson:"design" cql:"design" dynamo:"design"`
18 | CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
19 | UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
20 | }
21 |
22 | // AsAPIEmailTemplate to return email template as graphql response object
23 | func (e *EmailTemplate) AsAPIEmailTemplate() *model.EmailTemplate {
24 | id := e.ID
25 | if strings.Contains(id, Collections.EmailTemplate+"/") {
26 | id = strings.TrimPrefix(id, Collections.EmailTemplate+"/")
27 | }
28 | return &model.EmailTemplate{
29 | ID: id,
30 | EventName: e.EventName,
31 | Subject: e.Subject,
32 | Template: e.Template,
33 | Design: e.Design,
34 | CreatedAt: refs.NewInt64Ref(e.CreatedAt),
35 | UpdatedAt: refs.NewInt64Ref(e.UpdatedAt),
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/dashboard/src/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { lazy, Suspense } from 'react';
2 | import { Outlet, Route, Routes } from 'react-router-dom';
3 |
4 | import { useAuthContext } from '../contexts/AuthContext';
5 | import { DashboardLayout } from '../layouts/DashboardLayout';
6 | import EmailTemplates from '../pages/EmailTemplates';
7 |
8 | const Auth = lazy(() => import('../pages/Auth'));
9 | const Environment = lazy(() => import('../pages/Environment'));
10 | const Home = lazy(() => import('../pages/Home'));
11 | const Users = lazy(() => import('../pages/Users'));
12 | const Webhooks = lazy(() => import('../pages/Webhooks'));
13 |
14 | export const AppRoutes = () => {
15 | const { isLoggedIn } = useAuthContext();
16 |
17 | if (isLoggedIn) {
18 | return (
19 |
20 | >}>
21 |
22 |
25 |
26 |
27 | }
28 | >
29 | }>
30 | } />
31 | } />
32 |
33 | } />
34 | } />
35 | } />
36 | } />
37 |
38 |
39 |
40 |
41 | );
42 | }
43 | return (
44 | >}>
45 |
46 | } />
47 | } />
48 |
49 |
50 | );
51 | };
52 |
--------------------------------------------------------------------------------
/server/validators/password.go:
--------------------------------------------------------------------------------
1 | package validators
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/authorizerdev/authorizer/server/constants"
7 | "github.com/authorizerdev/authorizer/server/memorystore"
8 | )
9 |
10 | // ValidatePassword to validate the password against the following policy
11 | // min char length: 6
12 | // max char length: 36
13 | // at least one upper case letter
14 | // at least one lower case letter
15 | // at least one digit
16 | // at least one special character
17 | func IsValidPassword(password string) error {
18 | if len(password) < 6 || len(password) > 36 {
19 | return errors.New("password must be of minimum 6 characters and maximum 36 characters")
20 | }
21 |
22 | // if strong password is disabled
23 | // just check for min 6 chars & max 36
24 | isStrongPasswordDisabled, _ := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyDisableStrongPassword)
25 | if isStrongPasswordDisabled {
26 | return nil
27 | }
28 |
29 | hasUpperCase := false
30 | hasLowerCase := false
31 | hasDigit := false
32 | hasSpecialChar := false
33 |
34 | for _, char := range password {
35 | if char >= 'A' && char <= 'Z' {
36 | hasUpperCase = true
37 | } else if char >= 'a' && char <= 'z' {
38 | hasLowerCase = true
39 | } else if char >= '0' && char <= '9' {
40 | hasDigit = true
41 | } else {
42 | hasSpecialChar = true
43 | }
44 | }
45 |
46 | isValid := hasUpperCase && hasLowerCase && hasDigit && hasSpecialChar
47 |
48 | if isValid {
49 | return nil
50 | }
51 |
52 | return errors.New(`password is not valid. It needs to be at least 6 characters long and contain at least one number, one uppercase letter, one lowercase letter and one special character`)
53 | }
54 |
--------------------------------------------------------------------------------
/server/db/providers/dynamodb/env.go:
--------------------------------------------------------------------------------
1 | package dynamodb
2 |
3 | import (
4 | "context"
5 | "errors"
6 | "fmt"
7 | "time"
8 |
9 | "github.com/authorizerdev/authorizer/server/db/models"
10 | "github.com/google/uuid"
11 | )
12 |
13 | // AddEnv to save environment information in database
14 | func (p *provider) AddEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
15 | collection := p.db.Table(models.Collections.Env)
16 | if env.ID == "" {
17 | env.ID = uuid.New().String()
18 | }
19 | env.Key = env.ID
20 | env.CreatedAt = time.Now().Unix()
21 | env.UpdatedAt = time.Now().Unix()
22 | err := collection.Put(env).RunWithContext(ctx)
23 | if err != nil {
24 | return nil, err
25 | }
26 | return env, nil
27 | }
28 |
29 | // UpdateEnv to update environment information in database
30 | func (p *provider) UpdateEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
31 | collection := p.db.Table(models.Collections.Env)
32 | env.UpdatedAt = time.Now().Unix()
33 | err := UpdateByHashKey(collection, "id", env.ID, env)
34 | if err != nil {
35 | return nil, err
36 | }
37 | return env, nil
38 | }
39 |
40 | // GetEnv to get environment information from database
41 | func (p *provider) GetEnv(ctx context.Context) (*models.Env, error) {
42 | var env *models.Env
43 | collection := p.db.Table(models.Collections.Env)
44 | // As there is no Findone supported.
45 | iter := collection.Scan().Limit(1).Iter()
46 | for iter.NextWithContext(ctx, &env) {
47 | if env == nil {
48 | return nil, errors.New("no documets found")
49 | } else {
50 | return env, nil
51 | }
52 | }
53 | err := iter.Err()
54 | if err != nil {
55 | return env, fmt.Errorf("config not found")
56 | }
57 | return env, nil
58 | }
59 |
--------------------------------------------------------------------------------
/server/test/delete_email_template_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/authorizerdev/authorizer/server/constants"
8 | "github.com/authorizerdev/authorizer/server/crypto"
9 | "github.com/authorizerdev/authorizer/server/db"
10 | "github.com/authorizerdev/authorizer/server/graph/model"
11 | "github.com/authorizerdev/authorizer/server/memorystore"
12 | "github.com/authorizerdev/authorizer/server/resolvers"
13 | "github.com/stretchr/testify/assert"
14 | )
15 |
16 | func deleteEmailTemplateTest(t *testing.T, s TestSetup) {
17 | t.Helper()
18 | t.Run("should delete email templates", func(t *testing.T) {
19 | req, ctx := createContext(s)
20 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
21 | assert.NoError(t, err)
22 | h, err := crypto.EncryptPassword(adminSecret)
23 | assert.NoError(t, err)
24 | req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
25 |
26 | // get all email templates
27 | emailTemplates, err := db.Provider.ListEmailTemplate(ctx, &model.Pagination{
28 | Limit: 10,
29 | Page: 1,
30 | Offset: 0,
31 | })
32 | assert.NoError(t, err)
33 |
34 | for _, e := range emailTemplates.EmailTemplates {
35 | res, err := resolvers.DeleteEmailTemplateResolver(ctx, model.DeleteEmailTemplateRequest{
36 | ID: e.ID,
37 | })
38 |
39 | assert.NoError(t, err)
40 | assert.NotNil(t, res)
41 | assert.NotEmpty(t, res.Message)
42 | }
43 |
44 | emailTemplates, err = db.Provider.ListEmailTemplate(ctx, &model.Pagination{
45 | Limit: 10,
46 | Page: 1,
47 | Offset: 0,
48 | })
49 | assert.NoError(t, err)
50 | assert.Len(t, emailTemplates.EmailTemplates, 0)
51 | })
52 | }
53 |
--------------------------------------------------------------------------------
/server/test/verify_email_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/authorizerdev/authorizer/server/constants"
7 | "github.com/authorizerdev/authorizer/server/db"
8 | "github.com/authorizerdev/authorizer/server/graph/model"
9 | "github.com/authorizerdev/authorizer/server/refs"
10 | "github.com/authorizerdev/authorizer/server/resolvers"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | func verifyEmailTest(t *testing.T, s TestSetup) {
15 | t.Helper()
16 | t.Run(`should verify email`, func(t *testing.T) {
17 | _, ctx := createContext(s)
18 | email := "verify_email." + s.TestInfo.Email
19 | res, err := resolvers.SignupResolver(ctx, model.SignUpInput{
20 | Email: refs.NewStringRef(email),
21 | Password: s.TestInfo.Password,
22 | ConfirmPassword: s.TestInfo.Password,
23 | })
24 | assert.NoError(t, err)
25 | assert.NotNil(t, res)
26 | user := *res.User
27 | assert.Equal(t, email, refs.StringValue(user.Email))
28 | assert.Nil(t, res.AccessToken, "access token should be nil")
29 | verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup)
30 | assert.Nil(t, err)
31 | assert.Equal(t, email, verificationRequest.Email)
32 |
33 | verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
34 | Token: verificationRequest.Token,
35 | })
36 | assert.Nil(t, err)
37 | assert.NotEqual(t, verifyRes.AccessToken, "", "access token should not be empty")
38 | // Check if phone number is verified
39 | user1, err := db.Provider.GetUserByEmail(ctx, email)
40 | assert.NoError(t, err)
41 | assert.NotNil(t, user1)
42 | assert.NotNil(t, user1.EmailVerifiedAt)
43 | cleanData(email)
44 | })
45 | }
46 |
--------------------------------------------------------------------------------
/server/test/users_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/authorizerdev/authorizer/server/constants"
8 | "github.com/authorizerdev/authorizer/server/crypto"
9 | "github.com/authorizerdev/authorizer/server/graph/model"
10 | "github.com/authorizerdev/authorizer/server/memorystore"
11 | "github.com/authorizerdev/authorizer/server/refs"
12 | "github.com/authorizerdev/authorizer/server/resolvers"
13 | "github.com/stretchr/testify/assert"
14 | )
15 |
16 | func usersTest(t *testing.T, s TestSetup) {
17 | t.Helper()
18 | t.Run(`should get users list with admin secret only`, func(t *testing.T) {
19 | req, ctx := createContext(s)
20 | email := "users." + s.TestInfo.Email
21 | resolvers.SignupResolver(ctx, model.SignUpInput{
22 | Email: refs.NewStringRef(email),
23 | Password: s.TestInfo.Password,
24 | ConfirmPassword: s.TestInfo.Password,
25 | })
26 |
27 | limit := int64(10)
28 | page := int64(1)
29 | pagination := &model.PaginatedInput{
30 | Pagination: &model.PaginationInput{
31 | Limit: &limit,
32 | Page: &page,
33 | },
34 | }
35 |
36 | usersRes, err := resolvers.UsersResolver(ctx, pagination)
37 | assert.NotNil(t, err, "unauthorized")
38 | assert.Nil(t, usersRes)
39 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
40 | assert.Nil(t, err)
41 | h, err := crypto.EncryptPassword(adminSecret)
42 | assert.Nil(t, err)
43 | req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
44 |
45 | usersRes, err = resolvers.UsersResolver(ctx, pagination)
46 | assert.Nil(t, err)
47 | rLen := len(usersRes.Users)
48 | assert.GreaterOrEqual(t, rLen, 1)
49 |
50 | cleanData(email)
51 | })
52 | }
53 |
--------------------------------------------------------------------------------
/server/resolvers/deactivate_account.go:
--------------------------------------------------------------------------------
1 | package resolvers
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/authorizerdev/authorizer/server/constants"
8 | "github.com/authorizerdev/authorizer/server/db"
9 | "github.com/authorizerdev/authorizer/server/graph/model"
10 | "github.com/authorizerdev/authorizer/server/memorystore"
11 | "github.com/authorizerdev/authorizer/server/token"
12 | "github.com/authorizerdev/authorizer/server/utils"
13 | log "github.com/sirupsen/logrus"
14 | )
15 |
16 | // DeactivateAccountResolver is the resolver for the deactivate_account field.
17 | func DeactivateAccountResolver(ctx context.Context) (*model.Response, error) {
18 | var res *model.Response
19 | gc, err := utils.GinContextFromContext(ctx)
20 | if err != nil {
21 | log.Debug("Failed to get GinContext: ", err)
22 | return res, err
23 | }
24 | tokenData, err := token.GetUserIDFromSessionOrAccessToken(gc)
25 | if err != nil {
26 | log.Debug("Failed GetUserIDFromSessionOrAccessToken: ", err)
27 | return res, err
28 | }
29 | log := log.WithFields(log.Fields{
30 | "user_id": tokenData.UserID,
31 | })
32 | user, err := db.Provider.GetUserByID(ctx, tokenData.UserID)
33 | if err != nil {
34 | log.Debug("Failed to get user by id: ", err)
35 | return res, err
36 | }
37 | now := time.Now().Unix()
38 | user.RevokedTimestamp = &now
39 | user, err = db.Provider.UpdateUser(ctx, user)
40 | if err != nil {
41 | log.Debug("Failed to update user: ", err)
42 | return res, err
43 | }
44 | go func() {
45 | memorystore.Provider.DeleteAllUserSessions(user.ID)
46 | utils.RegisterEvent(ctx, constants.UserDeactivatedWebhookEvent, "", user)
47 | }()
48 | res = &model.Response{
49 | Message: `user account deactivated successfully`,
50 | }
51 | return res, nil
52 | }
53 |
--------------------------------------------------------------------------------
/server/resolvers/revoke_access.go:
--------------------------------------------------------------------------------
1 | package resolvers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 |
8 | log "github.com/sirupsen/logrus"
9 |
10 | "github.com/authorizerdev/authorizer/server/constants"
11 | "github.com/authorizerdev/authorizer/server/db"
12 | "github.com/authorizerdev/authorizer/server/graph/model"
13 | "github.com/authorizerdev/authorizer/server/memorystore"
14 | "github.com/authorizerdev/authorizer/server/token"
15 | "github.com/authorizerdev/authorizer/server/utils"
16 | )
17 |
18 | // RevokeAccessResolver is a resolver for revoking user access
19 | func RevokeAccessResolver(ctx context.Context, params model.UpdateAccessInput) (*model.Response, error) {
20 | var res *model.Response
21 |
22 | gc, err := utils.GinContextFromContext(ctx)
23 | if err != nil {
24 | log.Debug("Failed to get GinContext: ", err)
25 | return res, err
26 | }
27 |
28 | if !token.IsSuperAdmin(gc) {
29 | log.Debug("Not logged in as super admin")
30 | return res, fmt.Errorf("unauthorized")
31 | }
32 |
33 | log := log.WithFields(log.Fields{
34 | "user_id": params.UserID,
35 | })
36 | user, err := db.Provider.GetUserByID(ctx, params.UserID)
37 | if err != nil {
38 | log.Debug("Failed to get user by ID: ", err)
39 | return res, err
40 | }
41 |
42 | now := time.Now().Unix()
43 | user.RevokedTimestamp = &now
44 |
45 | user, err = db.Provider.UpdateUser(ctx, user)
46 | if err != nil {
47 | log.Debug("Failed to update user: ", err)
48 | return res, err
49 | }
50 |
51 | go func() {
52 | memorystore.Provider.DeleteAllUserSessions(user.ID)
53 | utils.RegisterEvent(ctx, constants.UserAccessRevokedWebhookEvent, "", user)
54 | }()
55 |
56 | res = &model.Response{
57 | Message: `user access revoked successfully`,
58 | }
59 |
60 | return res, nil
61 | }
62 |
--------------------------------------------------------------------------------
/server/db/providers/sql/authenticator.go:
--------------------------------------------------------------------------------
1 | package sql
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/google/uuid"
8 | "gorm.io/gorm/clause"
9 |
10 | "github.com/authorizerdev/authorizer/server/db/models"
11 | )
12 |
13 | func (p *provider) AddAuthenticator(ctx context.Context, authenticators *models.Authenticator) (*models.Authenticator, error) {
14 | exists, _ := p.GetAuthenticatorDetailsByUserId(ctx, authenticators.UserID, authenticators.Method)
15 | if exists != nil {
16 | return authenticators, nil
17 | }
18 |
19 | if authenticators.ID == "" {
20 | authenticators.ID = uuid.New().String()
21 | }
22 | authenticators.Key = authenticators.ID
23 | authenticators.CreatedAt = time.Now().Unix()
24 | authenticators.UpdatedAt = time.Now().Unix()
25 | res := p.db.Clauses(
26 | clause.OnConflict{
27 | UpdateAll: true,
28 | Columns: []clause.Column{{Name: "id"}},
29 | }).Create(&authenticators)
30 | if res.Error != nil {
31 | return nil, res.Error
32 | }
33 | return authenticators, nil
34 | }
35 |
36 | func (p *provider) UpdateAuthenticator(ctx context.Context, authenticators *models.Authenticator) (*models.Authenticator, error) {
37 | authenticators.UpdatedAt = time.Now().Unix()
38 | result := p.db.Save(&authenticators)
39 | if result.Error != nil {
40 | return authenticators, result.Error
41 | }
42 | return authenticators, nil
43 | }
44 |
45 | func (p *provider) GetAuthenticatorDetailsByUserId(ctx context.Context, userId string, authenticatorType string) (*models.Authenticator, error) {
46 | var authenticators models.Authenticator
47 | result := p.db.Where("user_id = ?", userId).Where("method = ?", authenticatorType).First(&authenticators)
48 | if result.Error != nil {
49 | return nil, result.Error
50 | }
51 | return &authenticators, nil
52 | }
53 |
--------------------------------------------------------------------------------
/server/constants/oauth_info_urls.go:
--------------------------------------------------------------------------------
1 | package constants
2 |
3 | const (
4 | // Ref: https://github.com/qor/auth/blob/master/providers/google/google.go
5 | // deprecated and not used. instead we follow open id approach for google login
6 | GoogleUserInfoURL = "https://www.googleapis.com/oauth2/v3/userinfo"
7 | // Ref: https://github.com/qor/auth/blob/master/providers/facebook/facebook.go#L18
8 | FacebookUserInfoURL = "https://graph.facebook.com/me?fields=id,first_name,last_name,name,email,picture&access_token="
9 | // Ref: https://docs.github.com/en/developers/apps/building-github-apps/identifying-and-authorizing-users-for-github-apps#3-your-github-app-accesses-the-api-with-the-users-access-token
10 | GithubUserInfoURL = "https://api.github.com/user"
11 | // Get github user emails when user info email is empty Ref: https://stackoverflow.com/a/35387123
12 | GithubUserEmails = "https://api.github.com/user/emails"
13 |
14 | // Ref: https://docs.microsoft.com/en-us/linkedin/shared/integrations/people/profile-api
15 | LinkedInUserInfoURL = "https://api.linkedin.com/v2/me?projection=(id,localizedFirstName,localizedLastName,emailAddress,profilePicture(displayImage~:playableStreams))"
16 | LinkedInEmailURL = "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))"
17 |
18 | TwitterUserInfoURL = "https://api.twitter.com/2/users/me?user.fields=id,name,profile_image_url,username"
19 |
20 | // RobloxUserInfoURL is the URL to get user info from Roblox
21 | RobloxUserInfoURL = "https://apis.roblox.com/oauth/v1/userinfo"
22 |
23 | DiscordUserInfoURL = "https://discord.com/api/oauth2/@me"
24 | // Get microsoft user info.
25 | // Ref: https://learn.microsoft.com/en-us/azure/active-directory/develop/userinfo
26 | MicrosoftUserInfoURL = "https://graph.microsoft.com/oidc/userinfo"
27 | )
28 |
--------------------------------------------------------------------------------
/server/test/webhook_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/authorizerdev/authorizer/server/constants"
8 | "github.com/authorizerdev/authorizer/server/crypto"
9 | "github.com/authorizerdev/authorizer/server/db"
10 | "github.com/authorizerdev/authorizer/server/graph/model"
11 | "github.com/authorizerdev/authorizer/server/memorystore"
12 | "github.com/authorizerdev/authorizer/server/refs"
13 | "github.com/authorizerdev/authorizer/server/resolvers"
14 | "github.com/stretchr/testify/assert"
15 | )
16 |
17 | func webhookTest(t *testing.T, s TestSetup) {
18 | t.Helper()
19 | t.Run("should get webhook", func(t *testing.T) {
20 | req, ctx := createContext(s)
21 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
22 | assert.NoError(t, err)
23 | h, err := crypto.EncryptPassword(adminSecret)
24 | assert.NoError(t, err)
25 | req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
26 |
27 | // get webhook by event name
28 | webhooks, err := db.Provider.GetWebhookByEventName(ctx, constants.UserCreatedWebhookEvent)
29 | assert.NoError(t, err)
30 | assert.NotNil(t, webhooks)
31 | assert.GreaterOrEqual(t, len(webhooks), 2)
32 | for _, webhook := range webhooks {
33 | res, err := resolvers.WebhookResolver(ctx, model.WebhookRequest{
34 | ID: webhook.ID,
35 | })
36 | assert.NoError(t, err)
37 | assert.Equal(t, res.ID, webhook.ID)
38 | assert.Equal(t, refs.StringValue(res.Endpoint), refs.StringValue(webhook.Endpoint))
39 | // assert.Equal(t, refs.StringValue(res.EventName), refs.StringValue(webhook.EventName))
40 | assert.Equal(t, refs.BoolValue(res.Enabled), refs.BoolValue(webhook.Enabled))
41 | assert.Len(t, res.Headers, len(webhook.Headers))
42 | }
43 | })
44 | }
45 |
--------------------------------------------------------------------------------
/server/smsproviders/twilio.go:
--------------------------------------------------------------------------------
1 | package smsproviders
2 |
3 | import (
4 | "github.com/authorizerdev/authorizer/server/constants"
5 | "github.com/authorizerdev/authorizer/server/memorystore"
6 | log "github.com/sirupsen/logrus"
7 | twilio "github.com/twilio/twilio-go"
8 | api "github.com/twilio/twilio-go/rest/api/v2010"
9 | )
10 |
11 | // SendSMS util to send sms
12 | // TODO: Should be restructured to interface when another provider is added
13 | func SendSMS(sendTo, messageBody string) error {
14 | twilioAPISecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwilioAPISecret)
15 | if err != nil || twilioAPISecret == "" {
16 | log.Debug("Failed to get api secret: ", err)
17 | return err
18 | }
19 | twilioAPIKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwilioAPIKey)
20 | if err != nil || twilioAPIKey == "" {
21 | log.Debug("Failed to get api key: ", err)
22 | return err
23 | }
24 | twilioSenderFrom, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwilioSender)
25 | if err != nil || twilioSenderFrom == "" {
26 | log.Debug("Failed to get sender: ", err)
27 | return err
28 | }
29 | // accountSID is not a must to send sms on twilio
30 | twilioAccountSID, _ := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyTwilioAccountSID)
31 | client := twilio.NewRestClientWithParams(twilio.ClientParams{
32 | Username: twilioAPIKey,
33 | Password: twilioAPISecret,
34 | AccountSid: twilioAccountSID,
35 | })
36 | message := &api.CreateMessageParams{}
37 | message.SetBody(messageBody)
38 | message.SetFrom(twilioSenderFrom)
39 | message.SetTo(sendTo)
40 |
41 | _, err = client.Api.CreateMessage(message)
42 |
43 | if err != nil {
44 | log.Debug("Failed to send sms: ", err)
45 | return err
46 | }
47 |
48 | return nil
49 | }
50 |
--------------------------------------------------------------------------------
/server/handlers/openid_config.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 |
6 | "github.com/authorizerdev/authorizer/server/constants"
7 | "github.com/authorizerdev/authorizer/server/memorystore"
8 | "github.com/authorizerdev/authorizer/server/parsers"
9 | )
10 |
11 | // OpenIDConfigurationHandler handler for open-id configurations
12 | func OpenIDConfigurationHandler() gin.HandlerFunc {
13 | return func(c *gin.Context) {
14 | issuer := parsers.GetHost(c)
15 | jwtType, _ := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyJwtType)
16 |
17 | c.JSON(200, gin.H{
18 | "issuer": issuer,
19 | "authorization_endpoint": issuer + "/authorize",
20 | "token_endpoint": issuer + "/oauth/token",
21 | "userinfo_endpoint": issuer + "/userinfo",
22 | "jwks_uri": issuer + "/.well-known/jwks.json",
23 | "registration_endpoint": issuer + "/app",
24 | "response_types_supported": []string{"code", "token", "id_token"},
25 | "scopes_supported": []string{"openid", "email", "profile"},
26 | "response_modes_supported": []string{"query", "fragment", "form_post", "web_message"},
27 | "subject_types_supported": []string{"public"},
28 | "id_token_signing_alg_values_supported": []string{jwtType},
29 | "claims_supported": []string{"aud", "exp", "iss", "iat", "sub", "given_name", "family_name", "middle_name", "nickname", "preferred_username", "picture", "email", "email_verified", "roles", "role", "gender", "birthdate", "phone_number", "phone_number_verified", "nonce", "updated_at", "created_at", "revoked_timestamp", "login_method", "signup_methods", "token_type"},
30 | })
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/server/handlers/userinfo.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 |
7 | "github.com/gin-gonic/gin"
8 | log "github.com/sirupsen/logrus"
9 |
10 | "github.com/authorizerdev/authorizer/server/db"
11 | "github.com/authorizerdev/authorizer/server/token"
12 | )
13 |
14 | func UserInfoHandler() gin.HandlerFunc {
15 | return func(gc *gin.Context) {
16 | accessToken, err := token.GetAccessToken(gc)
17 | if err != nil {
18 | log.Debug("Error getting access token: ", err)
19 | gc.JSON(http.StatusUnauthorized, gin.H{
20 | "error": err.Error(),
21 | })
22 | return
23 | }
24 | claims, err := token.ValidateAccessToken(gc, accessToken)
25 | if err != nil {
26 | log.Debug("Error validating access token: ", err)
27 | gc.JSON(http.StatusUnauthorized, gin.H{
28 | "error": err.Error(),
29 | })
30 | return
31 | }
32 | userID := claims["sub"].(string)
33 | user, err := db.Provider.GetUserByID(gc, userID)
34 | if err != nil {
35 | log.Debug("Error getting user: ", err)
36 | gc.JSON(http.StatusUnauthorized, gin.H{
37 | "error": err.Error(),
38 | })
39 | return
40 | }
41 | apiUser := user.AsAPIUser()
42 | userBytes, err := json.Marshal(apiUser)
43 | if err != nil {
44 | log.Debug("Error marshalling user: ", err)
45 | gc.JSON(http.StatusUnauthorized, gin.H{
46 | "error": err.Error(),
47 | })
48 | return
49 | }
50 | res := map[string]interface{}{}
51 | err = json.Unmarshal(userBytes, &res)
52 | if err != nil {
53 | log.Debug("Error un-marshalling user: ", err)
54 | gc.JSON(http.StatusUnauthorized, gin.H{
55 | "error": err.Error(),
56 | })
57 | return
58 | }
59 | // add sub field to user as per openid standards
60 | // https://github.com/authorizerdev/authorizer/issues/327
61 | res["sub"] = userID
62 | gc.JSON(http.StatusOK, res)
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/server/db/providers/cassandradb/env.go:
--------------------------------------------------------------------------------
1 | package cassandradb
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/authorizerdev/authorizer/server/db/models"
9 | "github.com/gocql/gocql"
10 | "github.com/google/uuid"
11 | )
12 |
13 | // AddEnv to save environment information in database
14 | func (p *provider) AddEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
15 | if env.ID == "" {
16 | env.ID = uuid.New().String()
17 | }
18 | env.CreatedAt = time.Now().Unix()
19 | env.UpdatedAt = time.Now().Unix()
20 | insertEnvQuery := fmt.Sprintf("INSERT INTO %s (id, env, hash, created_at, updated_at) VALUES ('%s', '%s', '%s', %d, %d)", KeySpace+"."+models.Collections.Env, env.ID, env.EnvData, env.Hash, env.CreatedAt, env.UpdatedAt)
21 | err := p.db.Query(insertEnvQuery).Exec()
22 | if err != nil {
23 | return nil, err
24 | }
25 |
26 | return env, nil
27 | }
28 |
29 | // UpdateEnv to update environment information in database
30 | func (p *provider) UpdateEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
31 | env.UpdatedAt = time.Now().Unix()
32 | updateEnvQuery := fmt.Sprintf("UPDATE %s SET env = '%s', updated_at = %d WHERE id = '%s'", KeySpace+"."+models.Collections.Env, env.EnvData, env.UpdatedAt, env.ID)
33 | err := p.db.Query(updateEnvQuery).Exec()
34 | if err != nil {
35 | return nil, err
36 | }
37 | return env, nil
38 | }
39 |
40 | // GetEnv to get environment information from database
41 | func (p *provider) GetEnv(ctx context.Context) (*models.Env, error) {
42 | var env models.Env
43 | query := fmt.Sprintf("SELECT id, env, hash, created_at, updated_at FROM %s LIMIT 1", KeySpace+"."+models.Collections.Env)
44 | err := p.db.Query(query).Consistency(gocql.One).Scan(&env.ID, &env.EnvData, &env.Hash, &env.CreatedAt, &env.UpdatedAt)
45 | if err != nil {
46 | return nil, err
47 | }
48 | return &env, nil
49 | }
50 |
--------------------------------------------------------------------------------
/server/db/providers/provider_template/email_template.go:
--------------------------------------------------------------------------------
1 | package provider_template
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/authorizerdev/authorizer/server/db/models"
8 | "github.com/authorizerdev/authorizer/server/graph/model"
9 | "github.com/google/uuid"
10 | )
11 |
12 | // AddEmailTemplate to add EmailTemplate
13 | func (p *provider) AddEmailTemplate(ctx context.Context, emailTemplate *models.EmailTemplate) (*model.EmailTemplate, error) {
14 | if emailTemplate.ID == "" {
15 | emailTemplate.ID = uuid.New().String()
16 | }
17 |
18 | emailTemplate.Key = emailTemplate.ID
19 | emailTemplate.CreatedAt = time.Now().Unix()
20 | emailTemplate.UpdatedAt = time.Now().Unix()
21 | return emailTemplate.AsAPIEmailTemplate(), nil
22 | }
23 |
24 | // UpdateEmailTemplate to update EmailTemplate
25 | func (p *provider) UpdateEmailTemplate(ctx context.Context, emailTemplate *models.EmailTemplate) (*model.EmailTemplate, error) {
26 | emailTemplate.UpdatedAt = time.Now().Unix()
27 | return emailTemplate.AsAPIEmailTemplate(), nil
28 | }
29 |
30 | // ListEmailTemplates to list EmailTemplate
31 | func (p *provider) ListEmailTemplate(ctx context.Context, pagination *model.Pagination) (*model.EmailTemplates, error) {
32 | return nil, nil
33 | }
34 |
35 | // GetEmailTemplateByID to get EmailTemplate by id
36 | func (p *provider) GetEmailTemplateByID(ctx context.Context, emailTemplateID string) (*model.EmailTemplate, error) {
37 | return nil, nil
38 | }
39 |
40 | // GetEmailTemplateByEventName to get EmailTemplate by event_name
41 | func (p *provider) GetEmailTemplateByEventName(ctx context.Context, eventName string) (*model.EmailTemplate, error) {
42 | return nil, nil
43 | }
44 |
45 | // DeleteEmailTemplate to delete EmailTemplate
46 | func (p *provider) DeleteEmailTemplate(ctx context.Context, emailTemplate *model.EmailTemplate) error {
47 | return nil
48 | }
49 |
--------------------------------------------------------------------------------
/server/test/deactivate_account_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/authorizerdev/authorizer/server/constants"
8 | "github.com/authorizerdev/authorizer/server/db"
9 | "github.com/authorizerdev/authorizer/server/graph/model"
10 | "github.com/authorizerdev/authorizer/server/refs"
11 | "github.com/authorizerdev/authorizer/server/resolvers"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | func deactivateAccountTests(t *testing.T, s TestSetup) {
16 | t.Helper()
17 | t.Run(`should deactiavte the user account with access token only`, func(t *testing.T) {
18 | req, ctx := createContext(s)
19 | email := "deactiavte_account." + s.TestInfo.Email
20 |
21 | resolvers.SignupResolver(ctx, model.SignUpInput{
22 | Email: refs.NewStringRef(email),
23 | Password: s.TestInfo.Password,
24 | ConfirmPassword: s.TestInfo.Password,
25 | })
26 | _, err := resolvers.DeactivateAccountResolver(ctx)
27 | assert.NotNil(t, err, "unauthorized")
28 | verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup)
29 | assert.NoError(t, err)
30 | assert.NotNil(t, verificationRequest)
31 | verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
32 | Token: verificationRequest.Token,
33 | })
34 | assert.NoError(t, err)
35 | assert.NotNil(t, verifyRes)
36 | s.GinContext.Request.Header.Set("Authorization", "Bearer "+*verifyRes.AccessToken)
37 | ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)
38 | _, err = resolvers.DeactivateAccountResolver(ctx)
39 | assert.NoError(t, err)
40 | s.GinContext.Request.Header.Set("Authorization", "")
41 | assert.Nil(t, err)
42 | _, err = resolvers.ProfileResolver(ctx)
43 | assert.NotNil(t, err, "unauthorized")
44 | cleanData(email)
45 | })
46 | }
47 |
--------------------------------------------------------------------------------
/server/test/delete_webhook_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/authorizerdev/authorizer/server/constants"
8 | "github.com/authorizerdev/authorizer/server/crypto"
9 | "github.com/authorizerdev/authorizer/server/db"
10 | "github.com/authorizerdev/authorizer/server/graph/model"
11 | "github.com/authorizerdev/authorizer/server/memorystore"
12 | "github.com/authorizerdev/authorizer/server/resolvers"
13 | "github.com/stretchr/testify/assert"
14 | )
15 |
16 | func deleteWebhookTest(t *testing.T, s TestSetup) {
17 | t.Helper()
18 | t.Run("should delete webhook", func(t *testing.T) {
19 | req, ctx := createContext(s)
20 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
21 | assert.NoError(t, err)
22 | h, err := crypto.EncryptPassword(adminSecret)
23 | assert.NoError(t, err)
24 | req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
25 |
26 | // get all webhooks
27 | webhooks, err := db.Provider.ListWebhook(ctx, &model.Pagination{
28 | Limit: 20,
29 | Page: 1,
30 | Offset: 0,
31 | })
32 | assert.NoError(t, err)
33 |
34 | for _, w := range webhooks.Webhooks {
35 | res, err := resolvers.DeleteWebhookResolver(ctx, model.WebhookRequest{
36 | ID: w.ID,
37 | })
38 |
39 | assert.NoError(t, err)
40 | assert.NotNil(t, res)
41 | assert.NotEmpty(t, res.Message)
42 | }
43 |
44 | webhooks, err = db.Provider.ListWebhook(ctx, &model.Pagination{
45 | Limit: 20,
46 | Page: 1,
47 | Offset: 0,
48 | })
49 | assert.NoError(t, err)
50 | assert.Len(t, webhooks.Webhooks, 0)
51 | webhookLogs, err := db.Provider.ListWebhookLogs(ctx, &model.Pagination{
52 | Limit: 100,
53 | Page: 1,
54 | Offset: 0,
55 | }, "")
56 | assert.NoError(t, err)
57 | assert.Len(t, webhookLogs.WebhookLogs, 0)
58 | })
59 | }
60 |
--------------------------------------------------------------------------------
/server/test/profile_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/authorizerdev/authorizer/server/constants"
8 | "github.com/authorizerdev/authorizer/server/db"
9 | "github.com/authorizerdev/authorizer/server/graph/model"
10 | "github.com/authorizerdev/authorizer/server/refs"
11 | "github.com/authorizerdev/authorizer/server/resolvers"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | func profileTests(t *testing.T, s TestSetup) {
16 | t.Helper()
17 | t.Run(`should get profile only access_token token`, func(t *testing.T) {
18 | req, ctx := createContext(s)
19 | email := "profile." + s.TestInfo.Email
20 |
21 | resolvers.SignupResolver(ctx, model.SignUpInput{
22 | Email: refs.NewStringRef(email),
23 | Password: s.TestInfo.Password,
24 | ConfirmPassword: s.TestInfo.Password,
25 | })
26 |
27 | _, err := resolvers.ProfileResolver(ctx)
28 | assert.NotNil(t, err, "unauthorized")
29 |
30 | verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup)
31 | assert.NoError(t, err)
32 | assert.NotNil(t, verificationRequest)
33 | verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
34 | Token: verificationRequest.Token,
35 | })
36 | assert.NoError(t, err)
37 | assert.NotNil(t, verifyRes.AccessToken)
38 |
39 | s.GinContext.Request.Header.Set("Authorization", "Bearer "+*verifyRes.AccessToken)
40 | ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)
41 | profileRes, err := resolvers.ProfileResolver(ctx)
42 | assert.Nil(t, err)
43 | assert.NotNil(t, profileRes)
44 | s.GinContext.Request.Header.Set("Authorization", "")
45 | newEmail := profileRes.Email
46 | assert.Equal(t, email, refs.StringValue(newEmail), "emails should be equal")
47 | cleanData(email)
48 | })
49 | }
50 |
--------------------------------------------------------------------------------
/server/token/admin_token.go:
--------------------------------------------------------------------------------
1 | package token
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/authorizerdev/authorizer/server/constants"
7 | "github.com/authorizerdev/authorizer/server/cookie"
8 | "github.com/authorizerdev/authorizer/server/crypto"
9 | "github.com/authorizerdev/authorizer/server/memorystore"
10 | "github.com/gin-gonic/gin"
11 | "golang.org/x/crypto/bcrypt"
12 | )
13 |
14 | // CreateAdminAuthToken creates the admin token based on secret key
15 | func CreateAdminAuthToken(tokenType string, c *gin.Context) (string, error) {
16 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
17 | if err != nil {
18 | return "", err
19 | }
20 | return crypto.EncryptPassword(adminSecret)
21 | }
22 |
23 | // GetAdminAuthToken helps in getting the admin token from the request cookie
24 | func GetAdminAuthToken(gc *gin.Context) (string, error) {
25 | token, err := cookie.GetAdminCookie(gc)
26 | if err != nil || token == "" {
27 | return "", fmt.Errorf("unauthorized")
28 | }
29 |
30 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
31 | if err != nil {
32 | return "", err
33 | }
34 | err = bcrypt.CompareHashAndPassword([]byte(token), []byte(adminSecret))
35 |
36 | if err != nil {
37 | return "", fmt.Errorf(`unauthorized`)
38 | }
39 |
40 | return token, nil
41 | }
42 |
43 | // IsSuperAdmin checks if user is super admin
44 | func IsSuperAdmin(gc *gin.Context) bool {
45 | token, err := GetAdminAuthToken(gc)
46 | if err != nil {
47 | secret := gc.Request.Header.Get("x-authorizer-admin-secret")
48 | if secret == "" {
49 | return false
50 | }
51 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
52 | if err != nil {
53 | return false
54 | }
55 | return secret == adminSecret
56 | }
57 |
58 | return token != ""
59 | }
60 |
--------------------------------------------------------------------------------
/server/db/models/webhook_log.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/authorizerdev/authorizer/server/graph/model"
7 | "github.com/authorizerdev/authorizer/server/refs"
8 | )
9 |
10 | // Note: any change here should be reflected in providers/casandra/provider.go as it does not have model support in collection creation
11 |
12 | // WebhookLog model for db
13 | type WebhookLog struct {
14 | Key string `json:"_key,omitempty" bson:"_key,omitempty" cql:"_key,omitempty" dynamo:"key,omitempty"` // for arangodb
15 | ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
16 | HttpStatus int64 `json:"http_status" bson:"http_status" cql:"http_status" dynamo:"http_status"`
17 | Response string `json:"response" bson:"response" cql:"response" dynamo:"response"`
18 | Request string `json:"request" bson:"request" cql:"request" dynamo:"request"`
19 | WebhookID string `gorm:"type:char(36)" json:"webhook_id" bson:"webhook_id" cql:"webhook_id" dynamo:"webhook_id" index:"webhook_id,hash"`
20 | CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
21 | UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
22 | }
23 |
24 | // AsAPIWebhookLog to return webhook log as graphql response object
25 | func (w *WebhookLog) AsAPIWebhookLog() *model.WebhookLog {
26 | id := w.ID
27 | if strings.Contains(id, Collections.WebhookLog+"/") {
28 | id = strings.TrimPrefix(id, Collections.WebhookLog+"/")
29 | }
30 | return &model.WebhookLog{
31 | ID: id,
32 | HTTPStatus: refs.NewInt64Ref(w.HttpStatus),
33 | Response: refs.NewStringRef(w.Response),
34 | Request: refs.NewStringRef(w.Request),
35 | WebhookID: refs.NewStringRef(w.WebhookID),
36 | CreatedAt: refs.NewInt64Ref(w.CreatedAt),
37 | UpdatedAt: refs.NewInt64Ref(w.UpdatedAt),
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/server/test/magic_link_login_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/authorizerdev/authorizer/server/constants"
8 | "github.com/authorizerdev/authorizer/server/db"
9 | "github.com/authorizerdev/authorizer/server/graph/model"
10 | "github.com/authorizerdev/authorizer/server/memorystore"
11 | "github.com/authorizerdev/authorizer/server/resolvers"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | func magicLinkLoginTests(t *testing.T, s TestSetup) {
16 | t.Helper()
17 | t.Run(`should login with magic link`, func(t *testing.T) {
18 | req, ctx := createContext(s)
19 | email := "magic_link_login." + s.TestInfo.Email
20 | memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableSignUp, true)
21 | _, err := resolvers.MagicLinkLoginResolver(ctx, model.MagicLinkLoginInput{
22 | Email: email,
23 | })
24 | assert.NotNil(t, err, "signup disabled")
25 |
26 | memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableSignUp, false)
27 | _, err = resolvers.MagicLinkLoginResolver(ctx, model.MagicLinkLoginInput{
28 | Email: email,
29 | })
30 | assert.Nil(t, err, "signup should be successful")
31 |
32 | verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeMagicLinkLogin)
33 | assert.NoError(t, err)
34 | assert.NotNil(t, verificationRequest)
35 | verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
36 | Token: verificationRequest.Token,
37 | })
38 | assert.NoError(t, err)
39 | assert.NotNil(t, verifyRes.AccessToken)
40 | s.GinContext.Request.Header.Set("Authorization", "Bearer "+*verifyRes.AccessToken)
41 | ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)
42 | _, err = resolvers.ProfileResolver(ctx)
43 | assert.Nil(t, err)
44 | s.GinContext.Request.Header.Set("Authorization", "")
45 | cleanData(email)
46 | })
47 | }
48 |
--------------------------------------------------------------------------------
/server/db/providers/provider_template/verification_requests.go:
--------------------------------------------------------------------------------
1 | package provider_template
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/authorizerdev/authorizer/server/db/models"
8 | "github.com/authorizerdev/authorizer/server/graph/model"
9 | "github.com/google/uuid"
10 | )
11 |
12 | // AddVerification to save verification request in database
13 | func (p *provider) AddVerificationRequest(ctx context.Context, verificationRequest *models.VerificationRequest) (*models.VerificationRequest, error) {
14 | if verificationRequest.ID == "" {
15 | verificationRequest.ID = uuid.New().String()
16 | }
17 |
18 | verificationRequest.CreatedAt = time.Now().Unix()
19 | verificationRequest.UpdatedAt = time.Now().Unix()
20 |
21 | return verificationRequest, nil
22 | }
23 |
24 | // GetVerificationRequestByToken to get verification request from database using token
25 | func (p *provider) GetVerificationRequestByToken(ctx context.Context, token string) (*models.VerificationRequest, error) {
26 | var verificationRequest *models.VerificationRequest
27 |
28 | return verificationRequest, nil
29 | }
30 |
31 | // GetVerificationRequestByEmail to get verification request by email from database
32 | func (p *provider) GetVerificationRequestByEmail(ctx context.Context, email string, identifier string) (*models.VerificationRequest, error) {
33 | var verificationRequest *models.VerificationRequest
34 |
35 | return verificationRequest, nil
36 | }
37 |
38 | // ListVerificationRequests to get list of verification requests from database
39 | func (p *provider) ListVerificationRequests(ctx context.Context, pagination *model.Pagination) (*model.VerificationRequests, error) {
40 | return nil, nil
41 | }
42 |
43 | // DeleteVerificationRequest to delete verification request from database
44 | func (p *provider) DeleteVerificationRequest(ctx context.Context, verificationRequest *models.VerificationRequest) error {
45 | return nil
46 | }
47 |
--------------------------------------------------------------------------------
/server/test/verification_requests_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/authorizerdev/authorizer/server/constants"
8 | "github.com/authorizerdev/authorizer/server/crypto"
9 | "github.com/authorizerdev/authorizer/server/graph/model"
10 | "github.com/authorizerdev/authorizer/server/memorystore"
11 | "github.com/authorizerdev/authorizer/server/refs"
12 | "github.com/authorizerdev/authorizer/server/resolvers"
13 | "github.com/stretchr/testify/assert"
14 | )
15 |
16 | func verificationRequestsTest(t *testing.T, s TestSetup) {
17 | t.Helper()
18 |
19 | t.Run(`should get verification requests with admin secret only`, func(t *testing.T) {
20 | req, ctx := createContext(s)
21 | email := "verification_requests." + s.TestInfo.Email
22 | res, err := resolvers.SignupResolver(ctx, model.SignUpInput{
23 | Email: refs.NewStringRef(email),
24 | Password: s.TestInfo.Password,
25 | ConfirmPassword: s.TestInfo.Password,
26 | })
27 | assert.NoError(t, err)
28 | assert.NotNil(t, res)
29 | limit := int64(10)
30 | page := int64(1)
31 | pagination := &model.PaginatedInput{
32 | Pagination: &model.PaginationInput{
33 | Limit: &limit,
34 | Page: &page,
35 | },
36 | }
37 |
38 | requests, err := resolvers.VerificationRequestsResolver(ctx, pagination)
39 | assert.NotNil(t, err, "unauthorized")
40 | assert.Nil(t, requests)
41 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
42 | assert.Nil(t, err)
43 |
44 | h, err := crypto.EncryptPassword(adminSecret)
45 | assert.Nil(t, err)
46 | req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
47 | requests, err = resolvers.VerificationRequestsResolver(ctx, pagination)
48 | assert.Nil(t, err)
49 | rLen := len(requests.VerificationRequests)
50 | assert.GreaterOrEqual(t, rLen, 1)
51 |
52 | cleanData(email)
53 | })
54 | }
55 |
--------------------------------------------------------------------------------
/dashboard/src/components/EnvComponents/OrganizationInfo.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Flex, Stack, Center, Text, useMediaQuery } from '@chakra-ui/react';
3 | import InputField from '../InputField';
4 | import { TextInputType } from '../../constants';
5 |
6 | const OrganizationInfo = ({ variables, setVariables }: any) => {
7 | const [isNotSmallerScreen] = useMediaQuery('(min-width:600px)');
8 | return (
9 |
10 | {' '}
11 |
12 | Organization Information
13 |
14 |
15 |
16 |
21 | Organization Name:
22 |
23 |
27 |
33 |
34 |
35 |
36 |
41 | Organization Logo:
42 |
43 |
47 |
53 |
54 |
55 |
56 |
57 | );
58 | };
59 |
60 | export default OrganizationInfo;
61 |
--------------------------------------------------------------------------------
/server/db/providers/mongodb/env.go:
--------------------------------------------------------------------------------
1 | package mongodb
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/authorizerdev/authorizer/server/db/models"
9 | "github.com/google/uuid"
10 | "go.mongodb.org/mongo-driver/bson"
11 | "go.mongodb.org/mongo-driver/mongo/options"
12 | )
13 |
14 | // AddEnv to save environment information in database
15 | func (p *provider) AddEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
16 | if env.ID == "" {
17 | env.ID = uuid.New().String()
18 | }
19 | env.CreatedAt = time.Now().Unix()
20 | env.UpdatedAt = time.Now().Unix()
21 | env.Key = env.ID
22 | configCollection := p.db.Collection(models.Collections.Env, options.Collection())
23 | _, err := configCollection.InsertOne(ctx, env)
24 | if err != nil {
25 | return nil, err
26 | }
27 | return env, nil
28 | }
29 |
30 | // UpdateEnv to update environment information in database
31 | func (p *provider) UpdateEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
32 | env.UpdatedAt = time.Now().Unix()
33 | configCollection := p.db.Collection(models.Collections.Env, options.Collection())
34 | _, err := configCollection.UpdateOne(ctx, bson.M{"_id": bson.M{"$eq": env.ID}}, bson.M{"$set": env}, options.MergeUpdateOptions())
35 | if err != nil {
36 | return nil, err
37 | }
38 | return env, nil
39 | }
40 |
41 | // GetEnv to get environment information from database
42 | func (p *provider) GetEnv(ctx context.Context) (*models.Env, error) {
43 | var env *models.Env
44 | configCollection := p.db.Collection(models.Collections.Env, options.Collection())
45 | cursor, err := configCollection.Find(ctx, bson.M{}, options.Find())
46 | if err != nil {
47 | return nil, err
48 | }
49 | defer cursor.Close(ctx)
50 | for cursor.Next(nil) {
51 | err := cursor.Decode(&env)
52 | if err != nil {
53 | return nil, err
54 | }
55 | }
56 | if env == nil {
57 | return env, fmt.Errorf("config not found")
58 | }
59 | return env, nil
60 | }
61 |
--------------------------------------------------------------------------------
/server/test/invite_member_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/authorizerdev/authorizer/server/constants"
8 | "github.com/authorizerdev/authorizer/server/crypto"
9 | "github.com/authorizerdev/authorizer/server/graph/model"
10 | "github.com/authorizerdev/authorizer/server/memorystore"
11 | "github.com/authorizerdev/authorizer/server/resolvers"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | func inviteUserTest(t *testing.T, s TestSetup) {
16 | t.Helper()
17 | t.Run(`should invite user successfully`, func(t *testing.T) {
18 | req, ctx := createContext(s)
19 | emails := []string{"invite_member1." + s.TestInfo.Email}
20 |
21 | // unauthorized error
22 | res, err := resolvers.InviteMembersResolver(ctx, model.InviteMemberInput{
23 | Emails: emails,
24 | })
25 |
26 | assert.Error(t, err)
27 | assert.Nil(t, res)
28 |
29 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
30 | assert.Nil(t, err)
31 |
32 | h, err := crypto.EncryptPassword(adminSecret)
33 | assert.Nil(t, err)
34 | req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
35 |
36 | // invalid emails test
37 | invalidEmailsTest := []string{
38 | "test",
39 | "test.com",
40 | }
41 | res, err = resolvers.InviteMembersResolver(ctx, model.InviteMemberInput{
42 | Emails: invalidEmailsTest,
43 | })
44 | assert.Error(t, err)
45 | assert.Nil(t, res)
46 | // valid test
47 | res, err = resolvers.InviteMembersResolver(ctx, model.InviteMemberInput{
48 | Emails: emails,
49 | })
50 | assert.Nil(t, err)
51 | assert.NotNil(t, res)
52 | assert.NotNil(t, res.Message)
53 | assert.NotNil(t, res.Users)
54 | // duplicate error test
55 | res, err = resolvers.InviteMembersResolver(ctx, model.InviteMemberInput{
56 | Emails: emails,
57 | })
58 | assert.Error(t, err)
59 | assert.Nil(t, res)
60 | cleanData(emails[0])
61 | })
62 | }
63 |
--------------------------------------------------------------------------------
/server/handlers/logout.go:
--------------------------------------------------------------------------------
1 | package handlers
2 |
3 | import (
4 | "encoding/json"
5 | "net/http"
6 | "strings"
7 |
8 | "github.com/gin-gonic/gin"
9 | log "github.com/sirupsen/logrus"
10 |
11 | "github.com/authorizerdev/authorizer/server/cookie"
12 | "github.com/authorizerdev/authorizer/server/crypto"
13 | "github.com/authorizerdev/authorizer/server/memorystore"
14 | "github.com/authorizerdev/authorizer/server/token"
15 | )
16 |
17 | // Handler to logout user
18 | func LogoutHandler() gin.HandlerFunc {
19 | return func(gc *gin.Context) {
20 | redirectURL := strings.TrimSpace(gc.Query("redirect_uri"))
21 | // get fingerprint hash
22 | fingerprintHash, err := cookie.GetSession(gc)
23 | if err != nil {
24 | log.Debug("Failed to get session: ", err)
25 | gc.JSON(http.StatusUnauthorized, gin.H{
26 | "error": err.Error(),
27 | })
28 | return
29 | }
30 |
31 | decryptedFingerPrint, err := crypto.DecryptAES(fingerprintHash)
32 | if err != nil {
33 | log.Debug("Failed to decrypt fingerprint: ", err)
34 | gc.JSON(http.StatusUnauthorized, gin.H{
35 | "error": err.Error(),
36 | })
37 | return
38 | }
39 |
40 | var sessionData token.SessionData
41 | err = json.Unmarshal([]byte(decryptedFingerPrint), &sessionData)
42 | if err != nil {
43 | log.Debug("Failed to decrypt fingerprint: ", err)
44 | gc.JSON(http.StatusUnauthorized, gin.H{
45 | "error": err.Error(),
46 | })
47 | return
48 | }
49 |
50 | userID := sessionData.Subject
51 | loginMethod := sessionData.LoginMethod
52 | sessionToken := userID
53 | if loginMethod != "" {
54 | sessionToken = loginMethod + ":" + userID
55 | }
56 |
57 | memorystore.Provider.DeleteUserSession(sessionToken, sessionData.Nonce)
58 | cookie.DeleteSession(gc)
59 |
60 | if redirectURL != "" {
61 | gc.Redirect(http.StatusFound, redirectURL)
62 | } else {
63 | gc.JSON(http.StatusOK, gin.H{
64 | "message": "Logged out successfully",
65 | })
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/server/db/providers/dynamodb/authenticator.go:
--------------------------------------------------------------------------------
1 | package dynamodb
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/google/uuid"
8 |
9 | "github.com/authorizerdev/authorizer/server/db/models"
10 | )
11 |
12 | func (p *provider) AddAuthenticator(ctx context.Context, authenticators *models.Authenticator) (*models.Authenticator, error) {
13 | exists, _ := p.GetAuthenticatorDetailsByUserId(ctx, authenticators.UserID, authenticators.Method)
14 | if exists != nil {
15 | return authenticators, nil
16 | }
17 |
18 | collection := p.db.Table(models.Collections.Authenticators)
19 | if authenticators.ID == "" {
20 | authenticators.ID = uuid.New().String()
21 | }
22 |
23 | authenticators.CreatedAt = time.Now().Unix()
24 | authenticators.UpdatedAt = time.Now().Unix()
25 | err := collection.Put(authenticators).RunWithContext(ctx)
26 | if err != nil {
27 | return nil, err
28 | }
29 | return authenticators, nil
30 | }
31 |
32 | func (p *provider) UpdateAuthenticator(ctx context.Context, authenticators *models.Authenticator) (*models.Authenticator, error) {
33 | collection := p.db.Table(models.Collections.Authenticators)
34 | if authenticators.ID != "" {
35 | authenticators.UpdatedAt = time.Now().Unix()
36 | err := UpdateByHashKey(collection, "id", authenticators.ID, authenticators)
37 | if err != nil {
38 | return nil, err
39 | }
40 | }
41 | return authenticators, nil
42 |
43 | }
44 |
45 | func (p *provider) GetAuthenticatorDetailsByUserId(ctx context.Context, userId string, authenticatorType string) (*models.Authenticator, error) {
46 | var authenticators *models.Authenticator
47 | collection := p.db.Table(models.Collections.Authenticators)
48 | iter := collection.Scan().Filter("'user_id' = ?", userId).Filter("'method' = ?", authenticatorType).Iter()
49 | for iter.NextWithContext(ctx, &authenticators) {
50 | return authenticators, nil
51 | }
52 | err := iter.Err()
53 | if err != nil {
54 | return nil, err
55 | }
56 | return authenticators, nil
57 | }
58 |
--------------------------------------------------------------------------------
/server/db/providers/arangodb/env.go:
--------------------------------------------------------------------------------
1 | package arangodb
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 |
8 | arangoDriver "github.com/arangodb/go-driver"
9 | "github.com/google/uuid"
10 |
11 | "github.com/authorizerdev/authorizer/server/db/models"
12 | )
13 |
14 | // AddEnv to save environment information in database
15 | func (p *provider) AddEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
16 | if env.ID == "" {
17 | env.ID = uuid.New().String()
18 | env.Key = env.ID
19 | }
20 |
21 | env.CreatedAt = time.Now().Unix()
22 | env.UpdatedAt = time.Now().Unix()
23 | configCollection, _ := p.db.Collection(ctx, models.Collections.Env)
24 | meta, err := configCollection.CreateDocument(arangoDriver.WithOverwrite(ctx), env)
25 | if err != nil {
26 | return nil, err
27 | }
28 | env.Key = meta.Key
29 | env.ID = meta.ID.String()
30 | return env, nil
31 | }
32 |
33 | // UpdateEnv to update environment information in database
34 | func (p *provider) UpdateEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
35 | env.UpdatedAt = time.Now().Unix()
36 | collection, _ := p.db.Collection(ctx, models.Collections.Env)
37 | meta, err := collection.UpdateDocument(ctx, env.Key, env)
38 | if err != nil {
39 | return nil, err
40 | }
41 |
42 | env.Key = meta.Key
43 | env.ID = meta.ID.String()
44 | return env, nil
45 | }
46 |
47 | // GetEnv to get environment information from database
48 | func (p *provider) GetEnv(ctx context.Context) (*models.Env, error) {
49 | var env *models.Env
50 | query := fmt.Sprintf("FOR d in %s RETURN d", models.Collections.Env)
51 | cursor, err := p.db.Query(ctx, query, nil)
52 | if err != nil {
53 | return nil, err
54 | }
55 | defer cursor.Close()
56 | for {
57 | if !cursor.HasMore() {
58 | if env == nil {
59 | return env, fmt.Errorf("config not found")
60 | }
61 | break
62 | }
63 | _, err := cursor.ReadDocument(ctx, &env)
64 | if err != nil {
65 | return nil, err
66 | }
67 | }
68 |
69 | return env, nil
70 | }
71 |
--------------------------------------------------------------------------------
/server/db/providers/provider_template/webhook.go:
--------------------------------------------------------------------------------
1 | package provider_template
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "strings"
7 | "time"
8 |
9 | "github.com/authorizerdev/authorizer/server/db/models"
10 | "github.com/authorizerdev/authorizer/server/graph/model"
11 | "github.com/google/uuid"
12 | )
13 |
14 | // AddWebhook to add webhook
15 | func (p *provider) AddWebhook(ctx context.Context, webhook *models.Webhook) (*model.Webhook, error) {
16 | if webhook.ID == "" {
17 | webhook.ID = uuid.New().String()
18 | }
19 | webhook.Key = webhook.ID
20 | webhook.CreatedAt = time.Now().Unix()
21 | webhook.UpdatedAt = time.Now().Unix()
22 | // Add timestamp to make event name unique for legacy version
23 | webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
24 | return webhook.AsAPIWebhook(), nil
25 | }
26 |
27 | // UpdateWebhook to update webhook
28 | func (p *provider) UpdateWebhook(ctx context.Context, webhook *models.Webhook) (*model.Webhook, error) {
29 | webhook.UpdatedAt = time.Now().Unix()
30 | // Event is changed
31 | if !strings.Contains(webhook.EventName, "-") {
32 | webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
33 | }
34 | return webhook.AsAPIWebhook(), nil
35 | }
36 |
37 | // ListWebhooks to list webhook
38 | func (p *provider) ListWebhook(ctx context.Context, pagination *model.Pagination) (*model.Webhooks, error) {
39 | return nil, nil
40 | }
41 |
42 | // GetWebhookByID to get webhook by id
43 | func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model.Webhook, error) {
44 | return nil, nil
45 | }
46 |
47 | // GetWebhookByEventName to get webhook by event_name
48 | func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) ([]*model.Webhook, error) {
49 | return nil, nil
50 | }
51 |
52 | // DeleteWebhook to delete webhook
53 | func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error {
54 | // Also delete webhook logs for given webhook id
55 | return nil
56 | }
57 |
--------------------------------------------------------------------------------
/server/test/webhook_logs_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | "time"
7 |
8 | "github.com/authorizerdev/authorizer/server/constants"
9 | "github.com/authorizerdev/authorizer/server/crypto"
10 | "github.com/authorizerdev/authorizer/server/graph/model"
11 | "github.com/authorizerdev/authorizer/server/memorystore"
12 | "github.com/authorizerdev/authorizer/server/refs"
13 | "github.com/authorizerdev/authorizer/server/resolvers"
14 | "github.com/stretchr/testify/assert"
15 | )
16 |
17 | func webhookLogsTest(t *testing.T, s TestSetup) {
18 | time.Sleep(30 * time.Second) // add sleep for webhooklogs to get generated as they are async
19 | t.Helper()
20 | t.Run("should get webhook logs", func(t *testing.T) {
21 | req, ctx := createContext(s)
22 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
23 | assert.NoError(t, err)
24 | h, err := crypto.EncryptPassword(adminSecret)
25 | assert.NoError(t, err)
26 | req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
27 |
28 | webhookLogs, err := resolvers.WebhookLogsResolver(ctx, nil)
29 | assert.NoError(t, err)
30 | assert.Greater(t, len(webhookLogs.WebhookLogs), 1)
31 |
32 | webhooks, err := resolvers.WebhooksResolver(ctx, &model.PaginatedInput{
33 | Pagination: &model.PaginationInput{
34 | Limit: refs.NewInt64Ref(20),
35 | },
36 | })
37 | assert.NoError(t, err)
38 | assert.NotEmpty(t, webhooks)
39 | for _, w := range webhooks.Webhooks {
40 | t.Run(fmt.Sprintf("should get webhook for webhook_id:%s", w.ID), func(t *testing.T) {
41 | webhookLogs, err := resolvers.WebhookLogsResolver(ctx, &model.ListWebhookLogRequest{
42 | WebhookID: &w.ID,
43 | })
44 | assert.NoError(t, err)
45 | assert.GreaterOrEqual(t, len(webhookLogs.WebhookLogs), 1, refs.StringValue(w.EventName))
46 | for _, wl := range webhookLogs.WebhookLogs {
47 | assert.Equal(t, refs.StringValue(wl.WebhookID), w.ID)
48 | }
49 | })
50 | }
51 | })
52 | }
53 |
--------------------------------------------------------------------------------
/server/test/update_profile_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "context"
5 | "testing"
6 |
7 | "github.com/authorizerdev/authorizer/server/constants"
8 | "github.com/authorizerdev/authorizer/server/db"
9 | "github.com/authorizerdev/authorizer/server/graph/model"
10 | "github.com/authorizerdev/authorizer/server/refs"
11 | "github.com/authorizerdev/authorizer/server/resolvers"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | func updateProfileTests(t *testing.T, s TestSetup) {
16 | t.Helper()
17 | t.Run(`should update the profile with access token only`, func(t *testing.T) {
18 | req, ctx := createContext(s)
19 | email := "update_profile." + s.TestInfo.Email
20 |
21 | resolvers.SignupResolver(ctx, model.SignUpInput{
22 | Email: refs.NewStringRef(email),
23 | Password: s.TestInfo.Password,
24 | ConfirmPassword: s.TestInfo.Password,
25 | })
26 |
27 | fName := "samani"
28 | _, err := resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{
29 | FamilyName: &fName,
30 | })
31 | assert.NotNil(t, err, "unauthorized")
32 |
33 | verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup)
34 | assert.NoError(t, err)
35 | assert.NotNil(t, verificationRequest)
36 | verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
37 | Token: verificationRequest.Token,
38 | })
39 | assert.NoError(t, err)
40 | assert.NotNil(t, verifyRes)
41 | s.GinContext.Request.Header.Set("Authorization", "Bearer "+*verifyRes.AccessToken)
42 | ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)
43 |
44 | newEmail := "new_" + email
45 | _, err = resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{
46 | Email: &newEmail,
47 | })
48 | s.GinContext.Request.Header.Set("Authorization", "")
49 | assert.Nil(t, err)
50 | _, err = resolvers.ProfileResolver(ctx)
51 | assert.NotNil(t, err, "unauthorized")
52 |
53 | cleanData(newEmail)
54 | cleanData(email)
55 | })
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BrowserRouter } from 'react-router-dom';
3 | import { AuthorizerProvider } from '@authorizerdev/authorizer-react';
4 | import Root from './Root';
5 | import { createRandomString } from './utils/common';
6 |
7 | declare global {
8 | interface Window {
9 | __authorizer__: any;
10 | }
11 | }
12 |
13 | export default function App() {
14 | const searchParams = new URLSearchParams(window.location.search);
15 | const state = searchParams.get('state') || createRandomString();
16 | const scope = searchParams.get('scope')
17 | ? searchParams.get('scope')?.toString().split(' ')
18 | : `openid profile email`;
19 |
20 | const urlProps: Record = {
21 | state,
22 | scope,
23 | };
24 |
25 | const redirectURL =
26 | searchParams.get('redirect_uri') || searchParams.get('redirectURL');
27 | if (redirectURL) {
28 | urlProps.redirectURL = redirectURL;
29 | } else {
30 | urlProps.redirectURL = window.location.href;
31 | }
32 | const globalState: Record = {
33 | ...window['__authorizer__'],
34 | ...urlProps,
35 | };
36 | return (
37 |
44 |
53 |

58 |
{globalState.organizationName}
59 |
60 |
61 |
62 |
68 |
69 |
70 |
71 |
72 |
73 | );
74 | }
75 |
--------------------------------------------------------------------------------
/server/test/add_webhook_test.go:
--------------------------------------------------------------------------------
1 | package test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | "time"
7 |
8 | "github.com/authorizerdev/authorizer/server/constants"
9 | "github.com/authorizerdev/authorizer/server/crypto"
10 | "github.com/authorizerdev/authorizer/server/graph/model"
11 | "github.com/authorizerdev/authorizer/server/memorystore"
12 | "github.com/authorizerdev/authorizer/server/refs"
13 | "github.com/authorizerdev/authorizer/server/resolvers"
14 | "github.com/stretchr/testify/assert"
15 | )
16 |
17 | func addWebhookTest(t *testing.T, s TestSetup) {
18 | t.Helper()
19 | t.Run("should add webhook", func(t *testing.T) {
20 | req, ctx := createContext(s)
21 | adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
22 | assert.NoError(t, err)
23 | h, err := crypto.EncryptPassword(adminSecret)
24 | assert.NoError(t, err)
25 | req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
26 | for _, eventType := range s.TestInfo.TestWebhookEventTypes {
27 | webhook, err := resolvers.AddWebhookResolver(ctx, model.AddWebhookRequest{
28 | EventName: eventType,
29 | Endpoint: s.TestInfo.WebhookEndpoint,
30 | Enabled: true,
31 | Headers: map[string]interface{}{
32 | "x-test": "foo",
33 | },
34 | })
35 | assert.NoError(t, err)
36 | assert.NotNil(t, webhook)
37 | assert.NotEmpty(t, webhook.Message)
38 | }
39 | time.Sleep(2 * time.Second)
40 | // Allow setting multiple webhooks for same event
41 | for _, eventType := range s.TestInfo.TestWebhookEventTypes {
42 | webhook, err := resolvers.AddWebhookResolver(ctx, model.AddWebhookRequest{
43 | EventName: eventType,
44 | Endpoint: s.TestInfo.WebhookEndpoint,
45 | Enabled: true,
46 | EventDescription: refs.NewStringRef(eventType + "-2"),
47 | Headers: map[string]interface{}{
48 | "x-test": "foo",
49 | },
50 | })
51 | assert.NoError(t, err)
52 | assert.NotNil(t, webhook)
53 | assert.NotEmpty(t, webhook.Message)
54 | }
55 | })
56 | }
57 |
--------------------------------------------------------------------------------
/server/cookie/admin_cookie.go:
--------------------------------------------------------------------------------
1 | package cookie
2 |
3 | import (
4 | "net/url"
5 |
6 | log "github.com/sirupsen/logrus"
7 |
8 | "github.com/authorizerdev/authorizer/server/constants"
9 | "github.com/authorizerdev/authorizer/server/memorystore"
10 | "github.com/authorizerdev/authorizer/server/parsers"
11 | "github.com/gin-gonic/gin"
12 | )
13 |
14 | // SetAdminCookie sets the admin cookie in the response
15 | func SetAdminCookie(gc *gin.Context, token string) {
16 | adminCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAdminCookieSecure)
17 | if err != nil {
18 | log.Debug("Error while getting admin cookie secure from env variable: %v", err)
19 | adminCookieSecure = true
20 | }
21 |
22 | secure := adminCookieSecure
23 | httpOnly := adminCookieSecure
24 | hostname := parsers.GetHost(gc)
25 | host, _ := parsers.GetHostParts(hostname)
26 | gc.SetCookie(constants.AdminCookieName, token, 3600, "/", host, secure, httpOnly)
27 | }
28 |
29 | // GetAdminCookie gets the admin cookie from the request
30 | func GetAdminCookie(gc *gin.Context) (string, error) {
31 | cookie, err := gc.Request.Cookie(constants.AdminCookieName)
32 | if err != nil {
33 | return "", err
34 | }
35 |
36 | // cookie escapes special characters like $
37 | // hence we need to unescape before comparing
38 | decodedValue, err := url.QueryUnescape(cookie.Value)
39 | if err != nil {
40 | return "", err
41 | }
42 | return decodedValue, nil
43 | }
44 |
45 | // DeleteAdminCookie sets the response cookie to empty
46 | func DeleteAdminCookie(gc *gin.Context) {
47 | adminCookieSecure, err := memorystore.Provider.GetBoolStoreEnvVariable(constants.EnvKeyAdminCookieSecure)
48 | if err != nil {
49 | log.Debug("Error while getting admin cookie secure from env variable: %v", err)
50 | adminCookieSecure = true
51 | }
52 |
53 | secure := adminCookieSecure
54 | httpOnly := adminCookieSecure
55 | hostname := parsers.GetHost(gc)
56 | host, _ := parsers.GetHostParts(hostname)
57 | gc.SetCookie(constants.AdminCookieName, "", -1, "/", host, secure, httpOnly)
58 | }
59 |
--------------------------------------------------------------------------------
/server/resolvers/add_email_template.go:
--------------------------------------------------------------------------------
1 | package resolvers
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "strings"
7 |
8 | "github.com/authorizerdev/authorizer/server/db"
9 | "github.com/authorizerdev/authorizer/server/db/models"
10 | "github.com/authorizerdev/authorizer/server/graph/model"
11 | "github.com/authorizerdev/authorizer/server/refs"
12 | "github.com/authorizerdev/authorizer/server/token"
13 | "github.com/authorizerdev/authorizer/server/utils"
14 | "github.com/authorizerdev/authorizer/server/validators"
15 | log "github.com/sirupsen/logrus"
16 | )
17 |
18 | // AddEmailTemplateResolver resolver for add email template mutation
19 | func AddEmailTemplateResolver(ctx context.Context, params model.AddEmailTemplateRequest) (*model.Response, error) {
20 | gc, err := utils.GinContextFromContext(ctx)
21 | if err != nil {
22 | log.Debug("Failed to get GinContext: ", err)
23 | return nil, err
24 | }
25 |
26 | if !token.IsSuperAdmin(gc) {
27 | log.Debug("Not logged in as super admin")
28 | return nil, fmt.Errorf("unauthorized")
29 | }
30 |
31 | if !validators.IsValidEmailTemplateEventName(params.EventName) {
32 | log.Debug("Invalid Event Name: ", params.EventName)
33 | return nil, fmt.Errorf("invalid event name %s", params.EventName)
34 | }
35 |
36 | if strings.TrimSpace(params.Subject) == "" {
37 | return nil, fmt.Errorf("empty subject not allowed")
38 | }
39 |
40 | if strings.TrimSpace(params.Template) == "" {
41 | return nil, fmt.Errorf("empty template not allowed")
42 | }
43 |
44 | var design string
45 |
46 | if params.Design == nil || strings.TrimSpace(refs.StringValue(params.Design)) == "" {
47 | design = ""
48 | }
49 |
50 | _, err = db.Provider.AddEmailTemplate(ctx, &models.EmailTemplate{
51 | EventName: params.EventName,
52 | Template: params.Template,
53 | Subject: params.Subject,
54 | Design: design,
55 | })
56 | if err != nil {
57 | log.Debug("Failed to add email template: ", err)
58 | return nil, err
59 | }
60 |
61 | return &model.Response{
62 | Message: `Email template added successfully`,
63 | }, nil
64 | }
65 |
--------------------------------------------------------------------------------
/server/db/providers/couchbase/env.go:
--------------------------------------------------------------------------------
1 | package couchbase
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/authorizerdev/authorizer/server/db/models"
9 | "github.com/couchbase/gocb/v2"
10 | "github.com/google/uuid"
11 | )
12 |
13 | // AddEnv to save environment information in database
14 | func (p *provider) AddEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
15 | if env.ID == "" {
16 | env.ID = uuid.New().String()
17 | }
18 | env.CreatedAt = time.Now().Unix()
19 | env.UpdatedAt = time.Now().Unix()
20 | env.Key = env.ID
21 | env.EncryptionKey = env.Hash
22 | insertOpt := gocb.InsertOptions{
23 | Context: ctx,
24 | }
25 | _, err := p.db.Collection(models.Collections.Env).Insert(env.ID, env, &insertOpt)
26 | if err != nil {
27 | return nil, err
28 | }
29 | return env, nil
30 | }
31 |
32 | // UpdateEnv to update environment information in database
33 | func (p *provider) UpdateEnv(ctx context.Context, env *models.Env) (*models.Env, error) {
34 | env.UpdatedAt = time.Now().Unix()
35 | env.EncryptionKey = env.Hash
36 |
37 | updateEnvQuery := fmt.Sprintf("UPDATE %s.%s SET env = $1, updated_at = $2 WHERE _id = $3", p.scopeName, models.Collections.Env)
38 | _, err := p.db.Query(updateEnvQuery, &gocb.QueryOptions{
39 | Context: ctx,
40 | PositionalParameters: []interface{}{env.EnvData, env.UpdatedAt, env.UpdatedAt, env.ID},
41 | })
42 | if err != nil {
43 | return nil, err
44 | }
45 | return env, nil
46 | }
47 |
48 | // GetEnv to get environment information from database
49 | func (p *provider) GetEnv(ctx context.Context) (*models.Env, error) {
50 | var env *models.Env
51 |
52 | query := fmt.Sprintf("SELECT _id, env, encryption_key, created_at, updated_at FROM %s.%s LIMIT 1", p.scopeName, models.Collections.Env)
53 | q, err := p.db.Query(query, &gocb.QueryOptions{
54 | Context: ctx,
55 | ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
56 | })
57 | if err != nil {
58 | return nil, err
59 | }
60 | err = q.One(&env)
61 | if err != nil {
62 | return nil, err
63 | }
64 | env.Hash = env.EncryptionKey
65 | return env, nil
66 | }
67 |
--------------------------------------------------------------------------------
/server/memorystore/providers/providers.go:
--------------------------------------------------------------------------------
1 | package providers
2 |
3 | // Provider defines current memory store provider
4 | type Provider interface {
5 | // SetUserSession sets the user session for given user identifier in form recipe:user_id
6 | SetUserSession(userId, key, token string, expiration int64) error
7 | // GetUserSession returns the session token for given token
8 | GetUserSession(userId, key string) (string, error)
9 | // DeleteUserSession deletes the user session
10 | DeleteUserSession(userId, key string) error
11 | // DeleteAllSessions deletes all the sessions from the session store
12 | DeleteAllUserSessions(userId string) error
13 | // DeleteSessionForNamespace deletes the session for a given namespace
14 | DeleteSessionForNamespace(namespace string) error
15 | // SetMfaSession sets the mfa session with key and value of userId
16 | SetMfaSession(userId, key string, expiration int64) error
17 | // GetMfaSession returns value of given mfa session
18 | GetMfaSession(userId, key string) (string, error)
19 | // DeleteMfaSession deletes given mfa session from in-memory store.
20 | DeleteMfaSession(userId, key string) error
21 |
22 | // SetState sets the login state (key, value form) in the session store
23 | SetState(key, state string) error
24 | // GetState returns the state from the session store
25 | GetState(key string) (string, error)
26 | // RemoveState removes the social login state from the session store
27 | RemoveState(key string) error
28 |
29 | // methods for env store
30 |
31 | // UpdateEnvStore to update the whole env store object
32 | UpdateEnvStore(store map[string]interface{}) error
33 | // GetEnvStore() returns the env store object
34 | GetEnvStore() (map[string]interface{}, error)
35 | // UpdateEnvVariable to update the particular env variable
36 | UpdateEnvVariable(key string, value interface{}) error
37 | // GetStringStoreEnvVariable to get the string env variable from env store
38 | GetStringStoreEnvVariable(key string) (string, error)
39 | // GetBoolStoreEnvVariable to get the bool env variable from env store
40 | GetBoolStoreEnvVariable(key string) (bool, error)
41 | }
42 |
--------------------------------------------------------------------------------
/server/db/providers/mongodb/authenticator.go:
--------------------------------------------------------------------------------
1 | package mongodb
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/google/uuid"
8 | "go.mongodb.org/mongo-driver/bson"
9 | "go.mongodb.org/mongo-driver/mongo/options"
10 |
11 | "github.com/authorizerdev/authorizer/server/db/models"
12 | )
13 |
14 | func (p *provider) AddAuthenticator(ctx context.Context, authenticators *models.Authenticator) (*models.Authenticator, error) {
15 | exists, _ := p.GetAuthenticatorDetailsByUserId(ctx, authenticators.UserID, authenticators.Method)
16 | if exists != nil {
17 | return authenticators, nil
18 | }
19 |
20 | if authenticators.ID == "" {
21 | authenticators.ID = uuid.New().String()
22 | }
23 | authenticators.CreatedAt = time.Now().Unix()
24 | authenticators.UpdatedAt = time.Now().Unix()
25 | authenticators.Key = authenticators.ID
26 | authenticatorsCollection := p.db.Collection(models.Collections.Authenticators, options.Collection())
27 | _, err := authenticatorsCollection.InsertOne(ctx, authenticators)
28 | if err != nil {
29 | return nil, err
30 | }
31 | return authenticators, nil
32 | }
33 |
34 | func (p *provider) UpdateAuthenticator(ctx context.Context, authenticators *models.Authenticator) (*models.Authenticator, error) {
35 | authenticators.UpdatedAt = time.Now().Unix()
36 | authenticatorsCollection := p.db.Collection(models.Collections.Authenticators, options.Collection())
37 | _, err := authenticatorsCollection.UpdateOne(ctx, bson.M{"_id": bson.M{"$eq": authenticators.ID}}, bson.M{"$set": authenticators})
38 | if err != nil {
39 | return nil, err
40 | }
41 | return authenticators, nil
42 | }
43 |
44 | func (p *provider) GetAuthenticatorDetailsByUserId(ctx context.Context, userId string, authenticatorType string) (*models.Authenticator, error) {
45 | var authenticators *models.Authenticator
46 | authenticatorsCollection := p.db.Collection(models.Collections.Authenticators, options.Collection())
47 | err := authenticatorsCollection.FindOne(ctx, bson.M{"user_id": userId, "method": authenticatorType}).Decode(&authenticators)
48 | if err != nil {
49 | return nil, err
50 | }
51 | return authenticators, nil
52 | }
53 |
--------------------------------------------------------------------------------