Compare commits

..

31 Commits
0.7.0 ... 0.8.5

Author SHA1 Message Date
Lakhan Samani
3d75a4a281 Fix bug getting github login meta data 2022-01-10 10:45:20 +05:30
Lakhan Samani
2d819f5d3c Update app 2022-01-08 23:01:52 +05:30
Lakhan Samani
3221740198 Fix issue with reset password 2022-01-08 23:01:06 +05:30
Lakhan Samani
1e759c64ed Fix email template 2022-01-08 18:44:19 +05:30
Lakhan Samani
b3c7f783ed Update tests 2022-01-08 18:16:26 +05:30
Lakhan Samani
37fe5071c5 Use gomail library for sending emails
3b881776b5
2022-01-08 14:08:42 +05:30
Lakhan Samani
2137d8ef5d Merge pull request #96 from authorizerdev/fix/smtp-send-mail
fix: send mail from param
2022-01-07 21:40:52 +05:30
Lakhan Samani
8178fa6b62 fix: send mail from param 2022-01-07 21:40:28 +05:30
Lakhan Samani
303a3cbbbe Merge pull request #95 from authorizerdev/smtp-improvements
Improve smtp variable names
2022-01-07 19:32:20 +05:30
Lakhan Samani
9173b340c8 Improve smtp variable names
_Rename:_

- `SENDER_EMAIL` -> `SMTP_USERNAME`
- `SENDER_PASSWORD` -> `SMTP_PASSWORD`
- `SENDER_EMAIL` -> Used as `From` in email

Resolves #94
2022-01-07 19:29:22 +05:30
Lakhan Samani
df192bed4d feat: allow disabling login page
Resolves #89
2021-12-29 05:41:39 +05:30
Lakhan Samani
8d2371c14e fix: spacing 2021-12-29 05:21:24 +05:30
Lakhan Samani
5ed669e0da Merge pull request #88 from authorizerdev/fix/app-code-spliting
Fix/app code spliting
2021-12-29 05:19:20 +05:30
Lakhan Samani
43dce69cce fix: update build script 2021-12-29 05:10:21 +05:30
Lakhan Samani
4c53eb97d2 fix: enable code spliting for app 2021-12-29 04:16:31 +05:30
Lakhan Samani
b4b8593879 fix: default role values 2021-12-24 18:42:32 +05:30
Lakhan Samani
46a91fde20 fix: version var in make file 2021-12-24 17:47:35 +05:30
Lakhan Samani
8f826e6c2f Update CONTRIBUTING.md 2021-12-24 10:55:07 +05:30
Lakhan Samani
ebfea707c5 Update README.md 2021-12-24 10:06:39 +05:30
Lakhan Samani
8fc0175166 Update CONTRIBUTING.md 2021-12-24 10:05:05 +05:30
Lakhan Samani
dc43f56db1 fix: update authorizer-react version 2021-12-24 09:55:24 +05:30
Lakhan Samani
e5761f1e42 Merge pull request #87 from authorizerdev/feat/use-opend-id-standard-claims
feat/use opend id standard claims
2021-12-24 08:49:01 +05:30
Lakhan Samani
8dd8252a46 fix: move test to __test__ folder 2021-12-24 07:40:04 +05:30
Lakhan Samani
7ee4715af2 fix: rename magic_link_login enum 2021-12-24 07:20:22 +05:30
Lakhan Samani
1b3f931074 fix: rename getresuser util 2021-12-24 06:35:02 +05:30
Lakhan Samani
30cde3e521 feat: add tests for all resolvers 2021-12-24 06:27:39 +05:30
Lakhan Samani
6e9370458b fix: create common resolver test suite 2021-12-23 14:17:44 +05:30
Lakhan Samani
beae4502d4 feat: add integration tests for signup, login, reset_password, forgot_password, verify_email 2021-12-23 10:31:52 +05:30
Lakhan Samani
969395ccdb fix: make email verification col nullable 2021-12-22 15:38:51 +05:30
Lakhan Samani
3ee79c3937 fix: unique constraint data 2021-12-22 15:31:45 +05:30
Lakhan Samani
508c714932 fix: refactor schema for open id claim standards 2021-12-22 10:51:12 +05:30
99 changed files with 3160 additions and 1668 deletions

View File

@@ -6,4 +6,6 @@ README.md
ROADMAP.md
build
.env
data.db
data.db
app/node_modules
app/build

View File

@@ -2,8 +2,12 @@ ENV=production
DATABASE_URL=data.db
DATABASE_TYPE=sqlite
ADMIN_SECRET=admin
DISABLE_EMAIL_VERIFICATION=true
JWT_SECRET=random_string
SENDER_EMAIL=info@authorizer.dev
SMTP_USERNAME=username
SMTP_PASSWORD=password
SMTP_HOST=smtp.mailtrap.io
SMTP_PORT=2525
JWT_TYPE=HS256
ROLES=user
DEFAULT_ROLES=user

View File

@@ -49,3 +49,192 @@ Please ask as many questions as you need, either directly in the issue or on [Di
5. Build the code `make clean && make`
> Note: if you don't have [`make`](https://www.ibm.com/docs/en/aix/7.2?topic=concepts-make-command), you can `cd` into `server` dir and build using the `go build` command
6. Run binary `./build/server`
### Testing
Make sure you test before creating PR.
If you want to test for all the databases that authorizer supports you will have to run `mongodb` & `arangodb` instances locally.
Setup mongodb & arangodb using Docker
```
docker run --name mongodb -d -p 27017:27017 mongo
docker run --name arangodb -d -p 8529:8529 -e ARANGO_ROOT_PASSWORD=root arangodb/arangodb:3.8.4
```
> Note: If you are not making any changes in db schema / db operations, you can disable those db tests [here](https://github.com/authorizerdev/authorizer/blob/main/server/__test__/resolvers_test.go#L14)
If you are adding new resolver,
1. create new resolver test file [here](https://github.com/authorizerdev/authorizer/tree/main/server/__test__)
Naming convention filename: `resolver_name_test.go` function name: `resolverNameTest(s TestSetup, t *testing.T)`
2. Add your tests [here](https://github.com/authorizerdev/authorizer/blob/main/server/__test__/resolvers_test.go#L38)
__Command to run tests:__
```sh
make test
```
__Manual Testing:__
For manually testing using graphql playground, you can paste following queries and mutations in your playground and test it
```gql
mutation Signup {
signup(params: {
email: "lakhan@yopmail.com",
password: "test",
confirm_password: "test",
given_name: "lakhan"
}) {
message
user {
id
family_name
given_name
email
email_verified
}
}
}
mutation ResendEamil {
resend_verify_email(params: {
email: "lakhan@yopmail.com"
identifier: "basic_auth_signup"
}) {
message
}
}
query GetVerifyRequests {
_verification_requests {
id
token
expires
identifier
}
}
mutation VerifyEmail {
verify_email(params: {
token: ""
}) {
access_token
expires_at
user {
id
email
given_name
email_verified
}
}
}
mutation Login {
login(params: {
email: "lakhan@yopmail.com",
password: "test"
}) {
access_token
expires_at
user {
id
family_name
given_name
email
}
}
}
query GetSession {
session {
access_token
expires_at
user {
id
given_name
family_name
email
email_verified
signup_methods
created_at
updated_at
}
}
}
mutation ForgotPassword {
forgot_password(params: {
email: "lakhan@yopmail.com"
}) {
message
}
}
mutation ResetPassword {
reset_password(params: {
token: ""
password: "test"
confirm_password: "test"
}) {
message
}
}
mutation UpdateProfile {
update_profile(params: {
family_name: "samani"
}) {
message
}
}
query GetUsers {
_users {
id
email
email_verified
given_name
family_name
picture
signup_methods
phone_number
}
}
mutation MagicLinkLogin {
magic_link_login(params: {
email: "test@yopmail.com"
}) {
message
}
}
mutation Logout {
logout {
message
}
}
mutation UpdateUser{
_update_user(params: {
id: "dafc9400-d603-4ade-997c-83fcd54bbd67",
roles: ["user", "admin"]
}) {
email
roles
}
}
mutation DeleteUser {
_delete_user(params: {
email: "signup.test134523@yopmail.com"
}) {
message
}
}
```

View File

@@ -8,26 +8,22 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- uses: actions/setup-go@v2
with:
go-version: '^1.17.3'
- name: Install dependencies
run: |
sudo apt-get install build-essential wget zip gcc-mingw-w64 && \
sudo apt-get remove --auto-remove golang-go && \
sudo rm -rf /usr/bin/go &&\
wget --progress=dot:mega https://golang.org/dl/go1.17.1.linux-amd64.tar.gz -O go-linux.tar.gz && \
sudo tar -zxf go-linux.tar.gz && \
sudo mv go /usr/bin/ && \
sudo mkdir -p /go/bin /go/src /go/pkg && \
export GO_HOME=/usr/bin/go && \
export GOPATH=/go && \
export PATH=${GOPATH}/bin:${GO_HOME}/bin/:$PATH && \
echo "/usr/bin/go/bin" >> $GITHUB_PATH
echo "/usr/bin/x86_64-w64-mingw32-gcc" >> GITHUB_PATH
go version && \
echo "/usr/bin/x86_64-w64-mingw32-gcc" >> GITHUB_PATH && \
wget --no-check-certificate --progress=dot:mega https://github.com/wangyoucao577/assets-uploader/releases/download/v0.3.0/github-assets-uploader-v0.3.0-linux-amd64.tar.gz -O github-assets-uploader.tar.gz && \
tar -zxf github-assets-uploader.tar.gz && \
sudo mv github-assets-uploader /usr/sbin/ && \
sudo rm -f github-assets-uploader.tar.gz && \
github-assets-uploader -version
github-assets-uploader -version && \
make build-app
- name: Print Go paths
run: whereis go
- name: Print Go Version

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@ server/server
server/.env
data
app/node_modules
app/build
build
.env
data.db

View File

@@ -1,5 +1,5 @@
FROM golang:1.17-alpine as builder
WORKDIR /app
FROM golang:1.17-alpine as go-builder
WORKDIR /authorizer
COPY server server
COPY Makefile .
@@ -7,15 +7,22 @@ ARG VERSION="latest"
ENV VERSION="$VERSION"
RUN echo "$VERSION"
RUN apk add build-base nodejs &&\
RUN apk add build-base &&\
make clean && make && \
chmod 777 build/server
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
FROM node:17-alpine3.12 as node-builder
WORKDIR /authorizer
COPY app app
COPY Makefile .
RUN apk add build-base &&\
make build-app
FROM alpine:latest
WORKDIR /root/
RUN mkdir app
COPY --from=node-builder /authorizer/app/build app/build
COPY --from=go-builder /authorizer/build build
COPY templates templates
COPY --from=builder /app/build build
EXPOSE 8080
CMD [ "./build/server" ]

View File

@@ -2,8 +2,10 @@ DEFAULT_VERSION=0.1.0-local
VERSION := $(or $(VERSION),$(DEFAULT_VERSION))
cmd:
cd server && go build -ldflags "-w -X main.Version=$(VERSION)" -o '../build/server'
cd server && go build -ldflags "-w -X main.VERSION=$(VERSION)" -o '../build/server'
build-app:
cd app && npm i && npm run build
clean:
rm -rf build
test:
cd server && go clean --testcache && go test -v ./...
cd server && go clean --testcache && go test -v ./__test__

View File

@@ -151,7 +151,7 @@ Deploy production ready Authorizer instance using [railway.app](https://github.c
## Testing
- Integration Test: This tests are written along with [authorizer-js](https://github.com/authorizerdev/authorizer-js/blob/main/__test__/index.test.js) lib.
- Check the testing instructions [here](https://github.com/authorizerdev/authorizer/blob/main/.github/CONTRIBUTING.md#testing)
## Integrating into your website

10
TODO.md
View File

@@ -1,5 +1,15 @@
# Task List
## Open ID compatible claims and schema
- [x] Rename `schema.graphqls` and re generate schema
- [x] Rename to snake case [files + schema]
- [x] Refactor db models
- [x] Check extra data in oauth profile and save accordingly
- [x] Update all the resolver to make them compatible with schema changes
- [x] Update JWT claims
- [x] Write integration tests for all resolvers
## Feature Multiple sessions
- Multiple sessions for users to login use hMset from redis for this

View File

@@ -1,16 +0,0 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #374151;
font-size: 14px;
}
*,
*:before,
*:after {
box-sizing: inherit;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

11
app/esbuild.config.js Normal file
View File

@@ -0,0 +1,11 @@
const __is_prod__ = process.env.NODE_ENV === 'production';
require('esbuild').build({
entryPoints: ['src/index.tsx'],
chunkNames: '[name]-[hash]',
bundle: true,
minify: __is_prod__,
outdir: 'build',
splitting: true,
format: 'esm',
watch: !__is_prod__,
});

158
app/package-lock.json generated
View File

@@ -5,119 +5,119 @@
"requires": true,
"dependencies": {
"@authorizerdev/authorizer-js": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.1.0.tgz",
"integrity": "sha512-wNtTrFYg4qjvI+Sm58d9E8x+XaiwCA0DCyG87e37YDXmMlvvOmRQf05dxRRxZoxNC1WUkA3UYMTAclCKkonZAg==",
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.2.0.tgz",
"integrity": "sha512-T2gurEYEP1T56j9IwDIWP1PsELoWcU7TBl5G0UsMqFQhKo7T6p2hM634Z7PS1yLegU3aihuvHGI0C0n4xSo0VQ==",
"requires": {
"node-fetch": "^2.6.1"
}
},
"@authorizerdev/authorizer-react": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.2.0.tgz",
"integrity": "sha512-1JICta0rpgDlncIcvilWcYKP8QNq2v0RlcYS6ZeYZGJ5bymFlF6NOdMjh6Hn2IFfSWtqEPQ6zU++kAQ8kei1AA==",
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.4.0.tgz",
"integrity": "sha512-ydE7xrP3cqeTtU923bK0+OIB1fnL0VHnbThcNa41n89XUPV3VBhZ23gxMg90nqon8JFE5g2TNz7+/qBCOQ7aZw==",
"requires": {
"@authorizerdev/authorizer-js": "^0.1.0",
"@authorizerdev/authorizer-js": "^0.2.0",
"final-form": "^4.20.2",
"react-final-form": "^6.5.3",
"styled-components": "^5.3.0"
}
},
"@babel/code-frame": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz",
"integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==",
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
"integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==",
"requires": {
"@babel/highlight": "^7.16.0"
"@babel/highlight": "^7.16.7"
}
},
"@babel/generator": {
"version": "7.16.5",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.5.tgz",
"integrity": "sha512-kIvCdjZqcdKqoDbVVdt5R99icaRtrtYhYK/xux5qiWCBmfdvEYMFZ68QCrpE5cbFM1JsuArUNs1ZkuKtTtUcZA==",
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.7.tgz",
"integrity": "sha512-/ST3Sg8MLGY5HVYmrjOgL60ENux/HfO/CsUh7y4MalThufhE/Ff/6EibFDHi4jiDCaWfJKoqbE6oTh21c5hrRg==",
"requires": {
"@babel/types": "^7.16.0",
"@babel/types": "^7.16.7",
"jsesc": "^2.5.1",
"source-map": "^0.5.0"
}
},
"@babel/helper-annotate-as-pure": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz",
"integrity": "sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg==",
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz",
"integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==",
"requires": {
"@babel/types": "^7.16.0"
"@babel/types": "^7.16.7"
}
},
"@babel/helper-environment-visitor": {
"version": "7.16.5",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.5.tgz",
"integrity": "sha512-ODQyc5AnxmZWm/R2W7fzhamOk1ey8gSguo5SGvF0zcB3uUzRpTRmM/jmLSm9bDMyPlvbyJ+PwPEK0BWIoZ9wjg==",
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz",
"integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==",
"requires": {
"@babel/types": "^7.16.0"
"@babel/types": "^7.16.7"
}
},
"@babel/helper-function-name": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz",
"integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==",
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz",
"integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==",
"requires": {
"@babel/helper-get-function-arity": "^7.16.0",
"@babel/template": "^7.16.0",
"@babel/types": "^7.16.0"
"@babel/helper-get-function-arity": "^7.16.7",
"@babel/template": "^7.16.7",
"@babel/types": "^7.16.7"
}
},
"@babel/helper-get-function-arity": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz",
"integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==",
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz",
"integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==",
"requires": {
"@babel/types": "^7.16.0"
"@babel/types": "^7.16.7"
}
},
"@babel/helper-hoist-variables": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz",
"integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==",
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz",
"integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==",
"requires": {
"@babel/types": "^7.16.0"
"@babel/types": "^7.16.7"
}
},
"@babel/helper-module-imports": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz",
"integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==",
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz",
"integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==",
"requires": {
"@babel/types": "^7.16.0"
"@babel/types": "^7.16.7"
}
},
"@babel/helper-split-export-declaration": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz",
"integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==",
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz",
"integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==",
"requires": {
"@babel/types": "^7.16.0"
"@babel/types": "^7.16.7"
}
},
"@babel/helper-validator-identifier": {
"version": "7.15.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
"integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w=="
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
"integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw=="
},
"@babel/highlight": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz",
"integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==",
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz",
"integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==",
"requires": {
"@babel/helper-validator-identifier": "^7.15.7",
"@babel/helper-validator-identifier": "^7.16.7",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
}
},
"@babel/parser": {
"version": "7.16.6",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.6.tgz",
"integrity": "sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ=="
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.7.tgz",
"integrity": "sha512-sR4eaSrnM7BV7QPzGfEX5paG/6wrZM3I0HDzfIAK06ESvo9oy3xBuVBxE3MbQaKNhvg8g/ixjMWo2CGpzpHsDA=="
},
"@babel/runtime": {
"version": "7.14.8",
@@ -128,38 +128,38 @@
}
},
"@babel/template": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz",
"integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==",
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz",
"integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==",
"requires": {
"@babel/code-frame": "^7.16.0",
"@babel/parser": "^7.16.0",
"@babel/types": "^7.16.0"
"@babel/code-frame": "^7.16.7",
"@babel/parser": "^7.16.7",
"@babel/types": "^7.16.7"
}
},
"@babel/traverse": {
"version": "7.16.5",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.5.tgz",
"integrity": "sha512-FOCODAzqUMROikDYLYxl4nmwiLlu85rNqBML/A5hKRVXG2LV8d0iMqgPzdYTcIpjZEBB7D6UDU9vxRZiriASdQ==",
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.7.tgz",
"integrity": "sha512-8KWJPIb8c2VvY8AJrydh6+fVRo2ODx1wYBU2398xJVq0JomuLBZmVQzLPBblJgHIGYG4znCpUZUZ0Pt2vdmVYQ==",
"requires": {
"@babel/code-frame": "^7.16.0",
"@babel/generator": "^7.16.5",
"@babel/helper-environment-visitor": "^7.16.5",
"@babel/helper-function-name": "^7.16.0",
"@babel/helper-hoist-variables": "^7.16.0",
"@babel/helper-split-export-declaration": "^7.16.0",
"@babel/parser": "^7.16.5",
"@babel/types": "^7.16.0",
"@babel/code-frame": "^7.16.7",
"@babel/generator": "^7.16.7",
"@babel/helper-environment-visitor": "^7.16.7",
"@babel/helper-function-name": "^7.16.7",
"@babel/helper-hoist-variables": "^7.16.7",
"@babel/helper-split-export-declaration": "^7.16.7",
"@babel/parser": "^7.16.7",
"@babel/types": "^7.16.7",
"debug": "^4.1.0",
"globals": "^11.1.0"
}
},
"@babel/types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz",
"integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==",
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.7.tgz",
"integrity": "sha512-E8HuV7FO9qLpx6OtoGfUQ2cjIYnbFwvZWYBS+87EwtdMvmUPJSwykpovFB+8insbpF0uJcpr8KMUi64XZntZcg==",
"requires": {
"@babel/helper-validator-identifier": "^7.15.7",
"@babel/helper-validator-identifier": "^7.16.7",
"to-fast-properties": "^2.0.0"
}
},
@@ -490,9 +490,9 @@
},
"dependencies": {
"@babel/runtime": {
"version": "7.16.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.5.tgz",
"integrity": "sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA==",
"version": "7.16.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz",
"integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==",
"requires": {
"regenerator-runtime": "^0.13.4"
}

View File

@@ -4,13 +4,14 @@
"description": "",
"main": "index.js",
"scripts": {
"build": "esbuild src/index.tsx --bundle --minify --sourcemap --outfile=build/bundle.js"
"build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js",
"start": "NODE_ENV=development node ./esbuild.config.js"
},
"keywords": [],
"author": "Lakhan Samani",
"license": "ISC",
"dependencies": {
"@authorizerdev/authorizer-react": "^0.2.0",
"@authorizerdev/authorizer-react": "latest",
"@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17",

View File

@@ -1,9 +1,10 @@
import React, { useEffect } from 'react';
import React, { useEffect, lazy, Suspense } from 'react';
import { Switch, Route } from 'react-router-dom';
import { useAuthorizer } from '@authorizerdev/authorizer-react';
import Dashboard from './pages/dashboard';
import Login from './pages/login';
import ResetPassword from './pages/rest-password';
const ResetPassword = lazy(() => import('./pages/rest-password'));
const Login = lazy(() => import('./pages/login'));
const Dashboard = lazy(() => import('./pages/dashboard'));
export default function Root() {
const { token, loading, config } = useAuthorizer();
@@ -24,22 +25,26 @@ export default function Root() {
if (token) {
return (
<Switch>
<Route path="/app" exact>
<Dashboard />
</Route>
</Switch>
<Suspense fallback={<></>}>
<Switch>
<Route path="/app" exact>
<Dashboard />
</Route>
</Switch>
</Suspense>
);
}
return (
<Switch>
<Route path="/app" exact>
<Login />
</Route>
<Route path="/app/reset-password">
<ResetPassword />
</Route>
</Switch>
<Suspense fallback={<></>}>
<Switch>
<Route path="/app" exact>
<Login />
</Route>
<Route path="/app/reset-password">
<ResetPassword />
</Route>
</Switch>
</Suspense>
);
}

16
app/src/index.css Normal file
View File

@@ -0,0 +1,16 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #374151;
font-size: 14px;
}
*,
*:before,
*:after {
box-sizing: inherit;
}

View File

@@ -1,5 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
ReactDOM.render(<App />, document.getElementById('root'));

View File

@@ -2,11 +2,11 @@ import React, { Fragment } from 'react';
import { AuthorizerResetPassword } from '@authorizerdev/authorizer-react';
export default function ResetPassword() {
return (
<Fragment>
<h1 style={{ textAlign: 'center' }}>Reset Password</h1>
<br />
<AuthorizerResetPassword />
</Fragment>
);
return (
<Fragment>
<h1 style={{ textAlign: 'center' }}>Reset Password</h1>
<br />
<AuthorizerResetPassword />
</Fragment>
);
}

View File

@@ -1,5 +1,5 @@
VERSION="$1"
make clean && CGO_ENABLED=1 VERSION=${VERSION} make
make clean && make build-app && CGO_ENABLED=1 VERSION=${VERSION} make
FILE_NAME=authorizer-${VERSION}-darwin-amd64.tar.gz
tar cvfz ${FILE_NAME} .env app/build build templates
AUTH="Authorization: token $GITHUB_TOKEN"

View File

@@ -0,0 +1,27 @@
package test
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCors(t *testing.T) {
allowedOrigin := "http://localhost:8080" // The allowed origin that you want to check
notAllowedOrigin := "http://myapp.com"
s := testSetup()
defer s.Server.Close()
client := &http.Client{}
req, _ := createContext(s)
req.Header.Add("Origin", allowedOrigin)
res, _ := client.Do(req)
// You should get your origin (or a * depending on your config) if the
// passed origin is allowed.
o := res.Header.Get("Access-Control-Allow-Origin")
assert.NotEqual(t, o, notAllowedOrigin, "Origins should not match")
assert.Equal(t, o, allowedOrigin, "Origins do match")
}

View File

@@ -0,0 +1,34 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func deleteUserTest(s TestSetup, t *testing.T) {
t.Run(`should delete users with admin secret only`, func(t *testing.T) {
req, ctx := createContext(s)
email := "delete_user." + s.TestInfo.Email
resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
_, err := resolvers.DeleteUser(ctx, model.DeleteUserInput{
Email: email,
})
assert.NotNil(t, err, "unauthorized")
req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET)
_, err = resolvers.DeleteUser(ctx, model.DeleteUserInput{
Email: email,
})
assert.Nil(t, err)
cleanData(email)
})
}

View File

@@ -1,23 +1,19 @@
package env
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/stretchr/testify/assert"
)
func TestEnvs(t *testing.T) {
constants.ENV_PATH = "../../.env.sample"
InitEnv()
assert.Equal(t, constants.ADMIN_SECRET, "admin")
assert.Equal(t, constants.ENV, "production")
assert.Equal(t, constants.DATABASE_URL, "data.db")
assert.Equal(t, constants.DATABASE_TYPE, enum.Sqlite.String())
assert.True(t, constants.DISABLE_EMAIL_VERIFICATION)
assert.True(t, constants.DISABLE_MAGIC_LOGIN)
assert.False(t, constants.DISABLE_EMAIL_VERIFICATION)
assert.False(t, constants.DISABLE_MAGIC_LINK_LOGIN)
assert.False(t, constants.DISABLE_BASIC_AUTHENTICATION)
assert.Equal(t, constants.JWT_TYPE, "HS256")
assert.Equal(t, constants.JWT_SECRET, "random_string")

View File

@@ -0,0 +1,35 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func forgotPasswordTest(s TestSetup, t *testing.T) {
t.Run(`should run forgot password`, func(t *testing.T) {
_, ctx := createContext(s)
email := "forgot_password." + s.TestInfo.Email
_, err := resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
_, err = resolvers.ForgotPassword(ctx, model.ForgotPasswordInput{
Email: email,
})
assert.Nil(t, err, "no errors for forgot password")
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.ForgotPassword.String())
assert.Nil(t, err)
assert.Equal(t, verificationRequest.Identifier, enum.ForgotPassword.String())
cleanData(email)
})
}

View File

@@ -0,0 +1,58 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func loginTests(s TestSetup, t *testing.T) {
t.Run(`should login`, func(t *testing.T) {
_, ctx := createContext(s)
email := "login." + s.TestInfo.Email
_, err := resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
_, err = resolvers.Login(ctx, model.LoginInput{
Email: email,
Password: s.TestInfo.Password,
})
assert.NotNil(t, err, "should fail because email is not verified")
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String())
resolvers.VerifyEmail(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
_, err = resolvers.Login(ctx, model.LoginInput{
Email: email,
Password: s.TestInfo.Password,
Roles: []string{"test"},
})
assert.NotNil(t, err, "invalid roles")
_, err = resolvers.Login(ctx, model.LoginInput{
Email: email,
Password: s.TestInfo.Password + "s",
})
assert.NotNil(t, err, "invalid password")
loginRes, err := resolvers.Login(ctx, model.LoginInput{
Email: email,
Password: s.TestInfo.Password,
})
assert.Nil(t, err, "login successful")
assert.NotNil(t, loginRes.AccessToken, "access token should not be empty")
cleanData(email)
})
}

View File

@@ -0,0 +1,35 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func logoutTests(s TestSetup, t *testing.T) {
t.Run(`should logout user`, func(t *testing.T) {
req, ctx := createContext(s)
email := "logout." + s.TestInfo.Email
_, err := resolvers.MagicLinkLogin(ctx, model.MagicLinkLoginInput{
Email: email,
})
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.MagicLinkLogin.String())
verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
token := *verifyRes.AccessToken
req.Header.Add("Authorization", "Bearer "+token)
_, err = resolvers.Logout(ctx)
assert.Nil(t, err)
_, err = resolvers.Profile(ctx)
assert.NotNil(t, err, "unauthorized")
cleanData(email)
})
}

View File

@@ -0,0 +1,35 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func magicLinkLoginTests(s TestSetup, t *testing.T) {
t.Run(`should login with magic link`, func(t *testing.T) {
req, ctx := createContext(s)
email := "magic_link_login." + s.TestInfo.Email
_, err := resolvers.MagicLinkLogin(ctx, model.MagicLinkLoginInput{
Email: email,
})
assert.Nil(t, err)
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.MagicLinkLogin.String())
verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
token := *verifyRes.AccessToken
req.Header.Add("Authorization", "Bearer "+token)
_, err = resolvers.Profile(ctx)
assert.Nil(t, err)
cleanData(email)
})
}

View File

@@ -0,0 +1,23 @@
package test
import (
"context"
"testing"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func metaTests(s TestSetup, t *testing.T) {
t.Run(`should get meta information`, func(t *testing.T) {
ctx := context.Background()
meta, err := resolvers.Meta(ctx)
assert.Nil(t, err)
assert.False(t, meta.IsFacebookLoginEnabled)
assert.False(t, meta.IsGoogleLoginEnabled)
assert.False(t, meta.IsGithubLoginEnabled)
assert.True(t, meta.IsEmailVerificationEnabled)
assert.True(t, meta.IsBasicAuthenticationEnabled)
assert.True(t, meta.IsMagicLinkLoginEnabled)
})
}

View File

@@ -0,0 +1,42 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func profileTests(s TestSetup, t *testing.T) {
t.Run(`should get profile only with token`, func(t *testing.T) {
req, ctx := createContext(s)
email := "profile." + s.TestInfo.Email
resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
_, err := resolvers.Profile(ctx)
assert.NotNil(t, err, "unauthorized")
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String())
verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
token := *verifyRes.AccessToken
req.Header.Add("Authorization", "Bearer "+token)
profileRes, err := resolvers.Profile(ctx)
assert.Nil(t, err)
newEmail := *&profileRes.Email
assert.Equal(t, email, newEmail, "emails should be equal")
cleanData(email)
})
}

View File

@@ -0,0 +1,31 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func resendVerifyEmailTests(s TestSetup, t *testing.T) {
t.Run(`should resend verification email`, func(t *testing.T) {
_, ctx := createContext(s)
email := "resend_verify_email." + s.TestInfo.Email
_, err := resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
_, err = resolvers.ResendVerifyEmail(ctx, model.ResendVerifyEmailInput{
Email: email,
Identifier: enum.BasicAuthSignup.String(),
})
assert.Nil(t, err)
cleanData(email)
})
}

View File

@@ -0,0 +1,49 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func resetPasswordTest(s TestSetup, t *testing.T) {
t.Run(`should reset password`, func(t *testing.T) {
email := "reset_password." + s.TestInfo.Email
_, ctx := createContext(s)
_, err := resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
_, err = resolvers.ForgotPassword(ctx, model.ForgotPasswordInput{
Email: email,
})
assert.Nil(t, err, "no errors for forgot password")
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.ForgotPassword.String())
assert.Nil(t, err, "should get forgot password request")
_, err = resolvers.ResetPassword(ctx, model.ResetPasswordInput{
Token: verificationRequest.Token,
Password: "test1",
ConfirmPassword: "test",
})
assert.NotNil(t, err, "passowrds don't match")
_, err = resolvers.ResetPassword(ctx, model.ResetPasswordInput{
Token: verificationRequest.Token,
Password: "test1",
ConfirmPassword: "test1",
})
assert.Nil(t, err, "password changed successfully")
cleanData(email)
})
}

View File

@@ -0,0 +1,47 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
)
func TestResolvers(t *testing.T) {
databases := map[string]string{
enum.Sqlite.String(): "../../data.db",
enum.Arangodb.String(): "http://root:root@localhost:8529",
enum.Mongodb.String(): "mongodb://localhost:27017",
}
for dbType, dbURL := range databases {
constants.DATABASE_URL = dbURL
constants.DATABASE_TYPE = dbType
db.InitDB()
s := testSetup()
defer s.Server.Close()
t.Run("should pass tests for "+dbType, func(t *testing.T) {
loginTests(s, t)
signupTests(s, t)
forgotPasswordTest(s, t)
resendVerifyEmailTests(s, t)
resetPasswordTest(s, t)
verifyEmailTest(s, t)
sessionTests(s, t)
profileTests(s, t)
updateProfileTests(s, t)
magicLinkLoginTests(s, t)
logoutTests(s, t)
metaTests(s, t)
// admin tests
verificationRequestsTest(s, t)
usersTest(s, t)
deleteUserTest(s, t)
updateUserTest(s, t)
})
}
}

View File

@@ -0,0 +1,42 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func sessionTests(s TestSetup, t *testing.T) {
t.Run(`should allow access to profile with session only`, func(t *testing.T) {
req, ctx := createContext(s)
email := "session." + s.TestInfo.Email
resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
_, err := resolvers.Session(ctx, []string{})
assert.NotNil(t, err, "unauthorized")
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String())
verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
token := *verifyRes.AccessToken
req.Header.Add("Authorization", "Bearer "+token)
sessionRes, err := resolvers.Session(ctx, []string{})
assert.Nil(t, err)
newToken := *sessionRes.AccessToken
assert.Equal(t, token, newToken, "tokens should be equal")
cleanData(email)
})
}

View File

@@ -0,0 +1,47 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func signupTests(s TestSetup, t *testing.T) {
t.Run(`should complete the signup and check duplicates`, func(t *testing.T) {
_, ctx := createContext(s)
email := "signup." + s.TestInfo.Email
res, err := resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password + "s",
})
assert.NotNil(t, err, "invalid password errors")
res, err = resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
user := *res.User
assert.Equal(t, email, user.Email)
assert.Nil(t, res.AccessToken, "access token should be nil")
res, err = resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
assert.NotNil(t, err, "should throw duplicate email error")
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String())
assert.Nil(t, err)
assert.Equal(t, email, verificationRequest.Email)
cleanData(email)
})
}

95
server/__test__/test.go Normal file
View File

@@ -0,0 +1,95 @@
package test
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/env"
"github.com/authorizerdev/authorizer/server/handlers"
"github.com/authorizerdev/authorizer/server/middlewares"
"github.com/authorizerdev/authorizer/server/session"
"github.com/gin-contrib/location"
"github.com/gin-gonic/gin"
)
// common user data to share across tests
type TestData struct {
Email string
Password string
}
type TestSetup struct {
GinEngine *gin.Engine
GinContext *gin.Context
Server *httptest.Server
TestInfo TestData
}
func cleanData(email string) {
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String())
if err == nil {
err = db.Mgr.DeleteVerificationRequest(verificationRequest)
}
verificationRequest, err = db.Mgr.GetVerificationByEmail(email, enum.ForgotPassword.String())
if err == nil {
err = db.Mgr.DeleteVerificationRequest(verificationRequest)
}
verificationRequest, err = db.Mgr.GetVerificationByEmail(email, enum.UpdateEmail.String())
if err == nil {
err = db.Mgr.DeleteVerificationRequest(verificationRequest)
}
dbUser, err := db.Mgr.GetUserByEmail(email)
if err == nil {
db.Mgr.DeleteUser(dbUser)
db.Mgr.DeleteUserSession(dbUser.ID)
}
}
func createContext(s TestSetup) (*http.Request, context.Context) {
req, _ := http.NewRequest(
"POST",
"http://"+s.Server.Listener.Addr().String()+"/graphql",
nil,
)
ctx := context.WithValue(req.Context(), "GinContextKey", s.GinContext)
s.GinContext.Request = req
return req, ctx
}
func testSetup() TestSetup {
testData := TestData{
Email: fmt.Sprintf("%d_authorizer_tester@yopmail.com", time.Now().Unix()),
Password: "test",
}
constants.ENV_PATH = "../../.env.sample"
env.InitEnv()
session.InitSession()
w := httptest.NewRecorder()
c, r := gin.CreateTestContext(w)
r.Use(location.Default())
r.Use(middlewares.GinContextToContextMiddleware())
r.Use(middlewares.CORSMiddleware())
r.POST("/graphql", handlers.GraphqlHandler())
server := httptest.NewServer(r)
return TestSetup{
GinEngine: r,
GinContext: c,
Server: server,
TestInfo: testData,
}
}

View File

@@ -0,0 +1,53 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func updateProfileTests(s TestSetup, t *testing.T) {
t.Run(`should update the profile with access token only`, func(t *testing.T) {
req, ctx := createContext(s)
email := "update_profile." + s.TestInfo.Email
resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
fName := "samani"
_, err := resolvers.UpdateProfile(ctx, model.UpdateProfileInput{
FamilyName: &fName,
})
assert.NotNil(t, err, "unauthorized")
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String())
verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
token := *verifyRes.AccessToken
req.Header.Add("Authorization", "Bearer "+token)
_, err = resolvers.UpdateProfile(ctx, model.UpdateProfileInput{
FamilyName: &fName,
})
assert.Nil(t, err)
newEmail := "new_" + email
_, err = resolvers.UpdateProfile(ctx, model.UpdateProfileInput{
Email: &newEmail,
})
assert.Nil(t, err)
_, err = resolvers.Profile(ctx)
assert.NotNil(t, err, "unauthorized")
cleanData(newEmail)
cleanData(email)
})
}

View File

@@ -0,0 +1,40 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func updateUserTest(s TestSetup, t *testing.T) {
t.Run(`should update the user with admin secret only`, func(t *testing.T) {
req, ctx := createContext(s)
email := "update_user." + s.TestInfo.Email
signupRes, _ := resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
user := *signupRes.User
adminRole := "admin"
userRole := "user"
newRoles := []*string{&adminRole, &userRole}
_, err := resolvers.UpdateUser(ctx, model.UpdateUserInput{
ID: user.ID,
Roles: newRoles,
})
assert.NotNil(t, err, "unauthorized")
req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET)
_, err = resolvers.UpdateUser(ctx, model.UpdateUserInput{
ID: user.ID,
Roles: newRoles,
})
assert.Nil(t, err)
cleanData(email)
})
}

View File

@@ -1,15 +1,16 @@
package utils
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
func TestGetHostName(t *testing.T) {
authorizer_url := "http://test.herokuapp.com:80"
host, port := GetHostParts(authorizer_url)
host, port := utils.GetHostParts(authorizer_url)
expectedHost := "test.herokuapp.com"
assert.Equal(t, host, expectedHost, "hostname should be equal")
@@ -19,7 +20,7 @@ func TestGetHostName(t *testing.T) {
func TestGetDomainName(t *testing.T) {
authorizer_url := "http://test.herokuapp.com"
got := GetDomainName(authorizer_url)
got := utils.GetDomainName(authorizer_url)
want := "herokuapp.com"
assert.Equal(t, got, want, "domain name should be equal")

View File

@@ -0,0 +1,33 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func usersTest(s TestSetup, t *testing.T) {
t.Run(`should get users list with admin secret only`, func(t *testing.T) {
req, ctx := createContext(s)
email := "users." + s.TestInfo.Email
resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
users, err := resolvers.Users(ctx)
assert.NotNil(t, err, "unauthorized")
req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET)
users, err = resolvers.Users(ctx)
assert.Nil(t, err)
rLen := len(users)
assert.GreaterOrEqual(t, rLen, 1)
cleanData(email)
})
}

View File

@@ -0,0 +1,43 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
func TestIsValidEmail(t *testing.T) {
validEmail := "lakhan@gmail.com"
invalidEmail1 := "lakhan"
invalidEmail2 := "lakhan.me"
assert.True(t, utils.IsValidEmail(validEmail), "it should be valid email")
assert.False(t, utils.IsValidEmail(invalidEmail1), "it should be invalid email")
assert.False(t, utils.IsValidEmail(invalidEmail2), "it should be invalid email")
}
func TestIsValidOrigin(t *testing.T) {
// don't use portocal(http/https) for ALLOWED_ORIGINS while testing,
// as we trim them off while running the main function
constants.ALLOWED_ORIGINS = []string{"localhost:8080", "*.google.com", "*.google.in", "*abc.*"}
assert.False(t, utils.IsValidOrigin("http://myapp.com"), "it should be invalid origin")
assert.False(t, utils.IsValidOrigin("http://appgoogle.com"), "it should be invalid origin")
assert.True(t, utils.IsValidOrigin("http://app.google.com"), "it should be valid origin")
assert.False(t, utils.IsValidOrigin("http://app.google.ind"), "it should be invalid origin")
assert.True(t, utils.IsValidOrigin("http://app.google.in"), "it should be valid origin")
assert.True(t, utils.IsValidOrigin("http://xyx.abc.com"), "it should be valid origin")
assert.True(t, utils.IsValidOrigin("http://xyx.abc.in"), "it should be valid origin")
assert.True(t, utils.IsValidOrigin("http://xyxabc.in"), "it should be valid origin")
assert.True(t, utils.IsValidOrigin("http://localhost:8080"), "it should be valid origin")
}
func TestIsValidIdentifier(t *testing.T) {
assert.False(t, utils.IsValidVerificationIdentifier("test"), "it should be invalid identifier")
assert.True(t, utils.IsValidVerificationIdentifier(enum.BasicAuthSignup.String()), "it should be valid identifier")
assert.True(t, utils.IsValidVerificationIdentifier(enum.UpdateEmail.String()), "it should be valid identifier")
assert.True(t, utils.IsValidVerificationIdentifier(enum.ForgotPassword.String()), "it should be valid identifier")
}

View File

@@ -0,0 +1,35 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func verificationRequestsTest(s TestSetup, t *testing.T) {
t.Run(`should get verification requests with admin secret only`, func(t *testing.T) {
req, ctx := createContext(s)
email := "verification_requests." + s.TestInfo.Email
resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
requests, err := resolvers.VerificationRequests(ctx)
assert.NotNil(t, err, "unauthorizer")
req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET)
requests, err = resolvers.VerificationRequests(ctx)
assert.Nil(t, err)
rLen := len(requests)
assert.GreaterOrEqual(t, rLen, 1)
cleanData(email)
})
}

View File

@@ -0,0 +1,38 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func verifyEmailTest(s TestSetup, t *testing.T) {
t.Run(`should verify email`, func(t *testing.T) {
_, ctx := createContext(s)
email := "verify_email." + s.TestInfo.Email
res, err := resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
user := *res.User
assert.Equal(t, email, user.Email)
assert.Nil(t, res.AccessToken, "access token should be nil")
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String())
assert.Nil(t, err)
assert.Equal(t, email, verificationRequest.Email)
verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
assert.Nil(t, err)
assert.NotEqual(t, verifyRes.AccessToken, "", "access token should not be empty")
cleanData(email)
})
}

View File

@@ -1,5 +1,6 @@
package constants
// this constants are configured via env
var (
ADMIN_SECRET = ""
ENV = ""
@@ -10,21 +11,23 @@ var (
DATABASE_NAME = ""
SMTP_HOST = ""
SMTP_PORT = ""
SMTP_USERNAME = ""
SMTP_PASSWORD = ""
SENDER_EMAIL = ""
SENDER_PASSWORD = ""
JWT_TYPE = ""
JWT_SECRET = ""
ALLOWED_ORIGINS = []string{}
AUTHORIZER_URL = ""
APP_URL = ""
PORT = "8080"
PORT = ""
REDIS_URL = ""
IS_PROD = false
COOKIE_NAME = ""
RESET_PASSWORD_URL = ""
DISABLE_EMAIL_VERIFICATION = false
DISABLE_BASIC_AUTHENTICATION = false
DISABLE_MAGIC_LOGIN = false
DISABLE_MAGIC_LINK_LOGIN = false
DISABLE_LOGIN_PAGE = false
// ROLES
ROLES = []string{}

View File

@@ -36,16 +36,12 @@ func initArangodb() (arangoDriver.Database, error) {
if arangodb_exists {
log.Println(constants.DATABASE_NAME + " db exists already")
arangodb, err = arangoClient.Database(nil, constants.DATABASE_NAME)
if err != nil {
return nil, err
}
} else {
arangodb, err = arangoClient.CreateDatabase(nil, constants.DATABASE_NAME, nil)
if err != nil {
return nil, err
}
@@ -61,11 +57,11 @@ func initArangodb() (arangoDriver.Database, error) {
}
}
userCollection, _ := arangodb.Collection(nil, Collections.User)
userCollection.EnsureHashIndex(ctx, []string{"id"}, &arangoDriver.EnsureHashIndexOptions{
userCollection.EnsureHashIndex(ctx, []string{"email"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
Sparse: true,
})
userCollection.EnsureHashIndex(ctx, []string{"email"}, &arangoDriver.EnsureHashIndexOptions{
userCollection.EnsureHashIndex(ctx, []string{"phone_number"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
Sparse: true,
})
@@ -79,11 +75,8 @@ func initArangodb() (arangoDriver.Database, error) {
log.Println("error creating collection("+Collections.VerificationRequest+"):", err)
}
}
verificationRequestCollection, _ := arangodb.Collection(nil, Collections.VerificationRequest)
verificationRequestCollection.EnsureHashIndex(ctx, []string{"id"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
Sparse: true,
})
verificationRequestCollection.EnsureHashIndex(ctx, []string{"email", "identifier"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
Sparse: true,
@@ -103,8 +96,7 @@ func initArangodb() (arangoDriver.Database, error) {
}
sessionCollection, _ := arangodb.Collection(nil, Collections.Session)
sessionCollection.EnsureHashIndex(ctx, []string{"id"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
sessionCollection.EnsureHashIndex(ctx, []string{"user_id"}, &arangoDriver.EnsureHashIndexOptions{
Sparse: true,
})

View File

@@ -26,8 +26,9 @@ type Manager interface {
GetVerificationByToken(token string) (VerificationRequest, error)
DeleteVerificationRequest(verificationRequest VerificationRequest) error
GetVerificationRequests() ([]VerificationRequest, error)
GetVerificationByEmail(email string) (VerificationRequest, error)
GetVerificationByEmail(email string, identifier string) (VerificationRequest, error)
AddSession(session Session) error
DeleteUserSession(userId string) error
}
type manager struct {
@@ -94,8 +95,8 @@ func InitDB() {
Mgr = &manager{
sqlDB: nil,
mongodb: nil,
arangodb: arangodb,
mongodb: nil,
}
break

View File

@@ -36,25 +36,21 @@ func initMongodb() (*mongo.Database, error) {
userCollection := mongodb.Collection(Collections.User, options.Collection())
userCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
mongo.IndexModel{
Keys: bson.M{"id": 1},
Keys: bson.M{"email": 1},
Options: options.Index().SetUnique(true).SetSparse(true),
},
}, options.CreateIndexes())
userCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
mongo.IndexModel{
Keys: bson.M{"email": 1},
Options: options.Index().SetUnique(true).SetSparse(true),
Keys: bson.M{"phone_number": 1},
Options: options.Index().SetUnique(true).SetSparse(true).SetPartialFilterExpression(map[string]interface{}{
"phone_number": map[string]string{"$type": "string"},
}),
},
}, options.CreateIndexes())
mongodb.CreateCollection(ctx, Collections.VerificationRequest, options.CreateCollection())
verificationRequestCollection := mongodb.Collection(Collections.VerificationRequest, options.Collection())
verificationRequestCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
mongo.IndexModel{
Keys: bson.M{"id": 1},
Options: options.Index().SetUnique(true).SetSparse(true),
},
}, options.CreateIndexes())
verificationRequestCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
mongo.IndexModel{
Keys: bson.M{"email": 1, "identifier": 1},
@@ -72,8 +68,8 @@ func initMongodb() (*mongo.Database, error) {
sessionCollection := mongodb.Collection(Collections.Session, options.Collection())
sessionCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
mongo.IndexModel{
Keys: bson.M{"id": 1},
Options: options.Index().SetUnique(true).SetSparse(true),
Keys: bson.M{"user_id": 1},
Options: options.Index().SetSparse(true),
},
}, options.CreateIndexes())

View File

@@ -1,19 +1,20 @@
package db
import (
"fmt"
"log"
"time"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
"gorm.io/gorm/clause"
)
type Session struct {
Key string `json:"_key,omitempty" bson:"_key,omitempty"` // for arangodb
ObjectID string `json:"_id,omitempty" bson:"_id"` // for arangodb & mongodb
ID string `gorm:"primaryKey;type:char(36)" json:"id" bson:"id"`
UserID string `gorm:"type:char(36)" json:"user_id" bson:"user_id"`
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
UserID string `gorm:"type:char(36),index:" json:"user_id" bson:"user_id"`
User User `json:"-" bson:"-"`
UserAgent string `json:"user_agent" bson:"user_agent"`
IP string `json:"ip" bson:"ip"`
@@ -29,7 +30,6 @@ func (mgr *manager) AddSession(session Session) error {
if IsORMSupported {
session.Key = session.ID
session.ObjectID = session.ID
res := mgr.sqlDB.Clauses(
clause.OnConflict{
DoNothing: true,
@@ -53,7 +53,6 @@ func (mgr *manager) AddSession(session Session) error {
if IsMongoDB {
session.Key = session.ID
session.ObjectID = session.ID
session.CreatedAt = time.Now().Unix()
session.UpdatedAt = time.Now().Unix()
sessionCollection := mgr.mongodb.Collection(Collections.Session, options.Collection())
@@ -66,3 +65,38 @@ func (mgr *manager) AddSession(session Session) error {
return nil
}
func (mgr *manager) DeleteUserSession(userId string) error {
if IsORMSupported {
result := mgr.sqlDB.Where("user_id = ?", userId).Delete(&Session{})
if result.Error != nil {
log.Println(`error deleting session:`, result.Error)
return result.Error
}
}
if IsArangoDB {
query := fmt.Sprintf(`FOR d IN %s FILTER d.user_id == @userId REMOVE { _key: d._key } IN %s`, Collections.Session, Collections.Session)
bindVars := map[string]interface{}{
"userId": userId,
}
cursor, err := mgr.arangodb.Query(nil, query, bindVars)
if err != nil {
log.Println("=> error deleting arangodb session:", err)
return err
}
defer cursor.Close()
}
if IsMongoDB {
sessionCollection := mgr.mongodb.Collection(Collections.Session, options.Collection())
_, err := sessionCollection.DeleteMany(nil, bson.M{"user_id": userId}, options.Delete())
if err != nil {
log.Println("error deleting session:", err)
return err
}
}
return nil
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/arangodb/go-driver"
arangoDriver "github.com/arangodb/go-driver"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
@@ -14,19 +15,25 @@ import (
)
type User struct {
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
ObjectID string `json:"_id,omitempty" bson:"_id"` // for arangodb & mongodb
ID string `gorm:"primaryKey;type:char(36)" json:"id" bson:"id"`
FirstName string `json:"first_name" bson:"first_name"`
LastName string `json:"last_name" bson:"last_name"`
Email string `gorm:"unique" json:"email" bson:"email"`
Password string `gorm:"type:text" json:"password" bson:"password"`
SignupMethod string `json:"signup_method" bson:"signup_method"`
EmailVerifiedAt int64 `json:"email_verified_at" bson:"email_verified_at"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
Image string `gorm:"type:text" json:"image" bson:"image"`
Roles string `json:"roles" bson:"roles"`
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
Email string `gorm:"unique" json:"email" bson:"email"`
EmailVerifiedAt *int64 `json:"email_verified_at" bson:"email_verified_at"`
Password *string `gorm:"type:text" json:"password" bson:"password"`
SignupMethods string `json:"signup_methods" bson:"signup_methods"`
GivenName *string `json:"given_name" bson:"given_name"`
FamilyName *string `json:"family_name" bson:"family_name"`
MiddleName *string `json:"middle_name" bson:"middle_name"`
Nickname *string `json:"nickname" bson:"nickname"`
Gender *string `json:"gender" bson:"gender"`
Birthdate *string `json:"birthdate" bson:"birthdate"`
PhoneNumber *string `gorm:"unique" json:"phone_number" bson:"phone_number"`
PhoneNumberVerifiedAt *int64 `json:"phone_number_verified_at" bson:"phone_number_verified_at"`
Picture *string `gorm:"type:text" json:"picture" bson:"picture"`
Roles string `json:"roles" bson:"roles"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
}
// AddUser function to add user even with email conflict
@@ -35,10 +42,13 @@ func (mgr *manager) AddUser(user User) (User, error) {
user.ID = uuid.New().String()
}
if user.Roles == "" {
user.Roles = constants.DEFAULT_ROLES[0]
}
if IsORMSupported {
// copy id as value for fields required for mongodb & arangodb
user.Key = user.ID
user.ObjectID = user.ID
result := mgr.sqlDB.Clauses(
clause.OnConflict{
UpdateAll: true,
@@ -61,14 +71,13 @@ func (mgr *manager) AddUser(user User) (User, error) {
return user, err
}
user.Key = meta.Key
user.ObjectID = meta.ID.String()
user.ID = meta.ID.String()
}
if IsMongoDB {
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
user.Key = user.ID
user.ObjectID = user.ID
userCollection := mgr.mongodb.Collection(Collections.User, options.Collection())
_, err := userCollection.InsertOne(nil, user)
if err != nil {
@@ -102,12 +111,12 @@ func (mgr *manager) UpdateUser(user User) (User, error) {
}
user.Key = meta.Key
user.ObjectID = meta.ID.String()
user.ID = meta.ID.String()
}
if IsMongoDB {
userCollection := mgr.mongodb.Collection(Collections.User, options.Collection())
_, err := userCollection.UpdateOne(nil, bson.M{"id": bson.M{"$eq": user.ID}}, bson.M{"$set": user}, options.MergeUpdateOptions())
_, err := userCollection.UpdateOne(nil, bson.M{"_id": bson.M{"$eq": user.ID}}, bson.M{"$set": user}, options.MergeUpdateOptions())
if err != nil {
log.Println("error updating user:", err)
return user, err
@@ -236,7 +245,7 @@ func (mgr *manager) GetUserByID(id string) (User, error) {
}
if IsArangoDB {
query := fmt.Sprintf("FOR d in %s FILTER d.id == @id LIMIT 1 RETURN d", Collections.User)
query := fmt.Sprintf("FOR d in %s FILTER d._id == @id LIMIT 1 RETURN d", Collections.User)
bindVars := map[string]interface{}{
"id": id,
}
@@ -263,7 +272,7 @@ func (mgr *manager) GetUserByID(id string) (User, error) {
if IsMongoDB {
userCollection := mgr.mongodb.Collection(Collections.User, options.Collection())
err := userCollection.FindOne(nil, bson.M{"id": id}).Decode(&user)
err := userCollection.FindOne(nil, bson.M{"_id": id}).Decode(&user)
if err != nil {
return user, err
}
@@ -293,7 +302,7 @@ func (mgr *manager) DeleteUser(user User) error {
if IsMongoDB {
userCollection := mgr.mongodb.Collection(Collections.User, options.Collection())
_, err := userCollection.DeleteOne(nil, bson.M{"id": user.ID}, options.Delete())
_, err := userCollection.DeleteOne(nil, bson.M{"_id": user.ID}, options.Delete())
if err != nil {
log.Println("error deleting user:", err)
return err

View File

@@ -14,8 +14,7 @@ import (
type VerificationRequest struct {
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
ObjectID string `json:"_id,omitempty" bson:"_id"` // for arangodb & mongodb
ID string `gorm:"primaryKey;type:char(36)" json:"id" bson:"id"`
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
Token string `gorm:"type:text" json:"token" bson:"token"`
Identifier string `gorm:"uniqueIndex:idx_email_identifier" json:"identifier" bson:"identifier"`
ExpiresAt int64 `json:"expires_at" bson:"expires_at"`
@@ -32,7 +31,6 @@ func (mgr *manager) AddVerification(verification VerificationRequest) (Verificat
if IsORMSupported {
// copy id as value for fields required for mongodb & arangodb
verification.Key = verification.ID
verification.ObjectID = verification.ID
result := mgr.sqlDB.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "email"}, {Name: "identifier"}},
DoUpdates: clause.AssignmentColumns([]string{"token", "expires_at"}),
@@ -54,14 +52,13 @@ func (mgr *manager) AddVerification(verification VerificationRequest) (Verificat
return verification, err
}
verification.Key = meta.Key
verification.ObjectID = meta.ID.String()
verification.ID = meta.ID.String()
}
if IsMongoDB {
verification.CreatedAt = time.Now().Unix()
verification.UpdatedAt = time.Now().Unix()
verification.Key = verification.ID
verification.ObjectID = verification.ID
verificationRequestCollection := mgr.mongodb.Collection(Collections.VerificationRequest, options.Collection())
_, err := verificationRequestCollection.InsertOne(nil, verification)
if err != nil {
@@ -182,10 +179,10 @@ func (mgr *manager) GetVerificationByToken(token string) (VerificationRequest, e
return verification, nil
}
func (mgr *manager) GetVerificationByEmail(email string) (VerificationRequest, error) {
func (mgr *manager) GetVerificationByEmail(email string, identifier string) (VerificationRequest, error) {
var verification VerificationRequest
if IsORMSupported {
result := mgr.sqlDB.Where("email = ?", email).First(&verification)
result := mgr.sqlDB.Where("email = ? AND identifier = ?", email, identifier).First(&verification)
if result.Error != nil {
log.Println(`error getting verification token:`, result.Error)
@@ -194,9 +191,10 @@ func (mgr *manager) GetVerificationByEmail(email string) (VerificationRequest, e
}
if IsArangoDB {
query := fmt.Sprintf("FOR d in %s FILTER d.email == @email LIMIT 1 RETURN d", Collections.VerificationRequest)
query := fmt.Sprintf("FOR d in %s FILTER d.email == @email FILTER d.identifier == @identifier LIMIT 1 RETURN d", Collections.VerificationRequest)
bindVars := map[string]interface{}{
"email": email,
"email": email,
"identifier": identifier,
}
cursor, err := mgr.arangodb.Query(nil, query, bindVars)
@@ -221,7 +219,7 @@ func (mgr *manager) GetVerificationByEmail(email string) (VerificationRequest, e
if IsMongoDB {
verificationRequestCollection := mgr.mongodb.Collection(Collections.VerificationRequest, options.Collection())
err := verificationRequestCollection.FindOne(nil, bson.M{"email": email}).Decode(&verification)
err := verificationRequestCollection.FindOne(nil, bson.M{"email": email, "identifier": identifier}).Decode(&verification)
if err != nil {
return verification, err
}
@@ -251,7 +249,7 @@ func (mgr *manager) DeleteVerificationRequest(verificationRequest VerificationRe
if IsMongoDB {
verificationRequestCollection := mgr.mongodb.Collection(Collections.VerificationRequest, options.Collection())
_, err := verificationRequestCollection.DeleteOne(nil, bson.M{"id": verificationRequest.ID}, options.Delete())
_, err := verificationRequestCollection.DeleteOne(nil, bson.M{"_id": verificationRequest.ID}, options.Delete())
if err != nil {
log.Println("error deleting verification request::", err)
return err

View File

@@ -1,90 +1,28 @@
package email
import (
"bytes"
"fmt"
"crypto/tls"
"log"
"mime/quotedprintable"
"net/smtp"
"strings"
"strconv"
"github.com/authorizerdev/authorizer/server/constants"
gomail "gopkg.in/mail.v2"
)
/**
Using: https://github.com/tangingw/go_smtp/blob/master/send_mail.go
For gmail add instruction to enable less security
// https://myaccount.google.com/u/0/lesssecureapps
// https://www.google.com/settings/security/lesssecureapps
// https://stackoverflow.com/questions/19877246/nodemailer-with-gmail-and-nodejs
**/
// TODO -> try using gomail.v2
type Sender struct {
User string
Password string
}
func NewSender() Sender {
return Sender{User: constants.SENDER_EMAIL, Password: constants.SENDER_PASSWORD}
}
func (sender Sender) SendMail(Dest []string, Subject, bodyMessage string) error {
msg := "From: " + sender.User + "\n" +
"To: " + strings.Join(Dest, ",") + "\n" +
"Subject: " + Subject + "\n" + bodyMessage
err := smtp.SendMail(constants.SMTP_HOST+":"+constants.SMTP_PORT,
smtp.PlainAuth("", sender.User, sender.Password, constants.SMTP_HOST),
sender.User, Dest, []byte(msg))
if err != nil {
func SendMail(to []string, Subject, bodyMessage string) error {
m := gomail.NewMessage()
m.SetHeader("From", constants.SENDER_EMAIL)
m.SetHeader("To", to...)
m.SetHeader("Subject", Subject)
m.SetBody("text/html", bodyMessage)
port, _ := strconv.Atoi(constants.SMTP_PORT)
d := gomail.NewDialer(constants.SMTP_HOST, port, constants.SMTP_USERNAME, constants.SMTP_PASSWORD)
if constants.ENV == "development" {
d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
}
if err := d.DialAndSend(m); err != nil {
log.Printf("smtp error: %s", err)
return err
}
return nil
}
func (sender Sender) WriteEmail(dest []string, contentType, subject, bodyMessage string) string {
header := make(map[string]string)
header["From"] = sender.User
receipient := ""
for _, user := range dest {
receipient = receipient + user
}
header["To"] = receipient
header["Subject"] = subject
header["MIME-Version"] = "1.0"
header["Content-Type"] = fmt.Sprintf("%s; charset=\"utf-8\"", contentType)
header["Content-Transfer-Encoding"] = "quoted-printable"
header["Content-Disposition"] = "inline"
message := ""
for key, value := range header {
message += fmt.Sprintf("%s: %s\r\n", key, value)
}
var encodedMessage bytes.Buffer
finalMessage := quotedprintable.NewWriter(&encodedMessage)
finalMessage.Write([]byte(bodyMessage))
finalMessage.Close()
message += "\r\n" + encodedMessage.String()
return message
}
func (sender *Sender) WriteHTMLEmail(dest []string, subject, bodyMessage string) string {
return sender.WriteEmail(dest, "text/html", subject, bodyMessage)
}
func (sender *Sender) WritePlainEmail(dest []string, subject, bodyMessage string) string {
return sender.WriteEmail(dest, "text/plain", subject, bodyMessage)
}

View File

@@ -4,7 +4,7 @@ type SignupMethod int
const (
BasicAuth SignupMethod = iota
MagicLink
MagicLinkLogin
Google
Github
Facebook
@@ -13,7 +13,7 @@ const (
func (d SignupMethod) String() string {
return [...]string{
"basic_auth",
"magic_link",
"magic_link_login",
"google",
"github",
"facebook",

238
server/env/env.go vendored
View File

@@ -1,7 +1,6 @@
package env
import (
"flag"
"log"
"os"
"strings"
@@ -13,7 +12,6 @@ import (
// build variables
var (
VERSION string
ARG_DB_URL *string
ARG_DB_TYPE *string
ARG_AUTHORIZER_URL *string
@@ -25,13 +23,8 @@ func InitEnv() {
if constants.ENV_PATH == "" {
constants.ENV_PATH = `.env`
}
ARG_DB_URL = flag.String("database_url", "", "Database connection string")
ARG_DB_TYPE = flag.String("database_type", "", "Database type, possible values are postgres,mysql,sqlite")
ARG_AUTHORIZER_URL = flag.String("authorizer_url", "", "URL for authorizer instance, eg: https://xyz.herokuapp.com")
ARG_ENV_FILE = flag.String("env_file", "", "Env file path")
flag.Parse()
if *ARG_ENV_FILE != "" {
if ARG_ENV_FILE != nil && *ARG_ENV_FILE != "" {
constants.ENV_PATH = *ARG_ENV_FILE
}
@@ -40,49 +33,154 @@ func InitEnv() {
log.Printf("error loading %s file", constants.ENV_PATH)
}
constants.VERSION = VERSION
constants.ADMIN_SECRET = os.Getenv("ADMIN_SECRET")
constants.ENV = os.Getenv("ENV")
constants.DATABASE_TYPE = os.Getenv("DATABASE_TYPE")
constants.DATABASE_URL = os.Getenv("DATABASE_URL")
constants.DATABASE_NAME = os.Getenv("DATABASE_NAME")
constants.SMTP_HOST = os.Getenv("SMTP_HOST")
constants.SMTP_PORT = os.Getenv("SMTP_PORT")
constants.SENDER_EMAIL = os.Getenv("SENDER_EMAIL")
constants.SENDER_PASSWORD = os.Getenv("SENDER_PASSWORD")
constants.JWT_SECRET = os.Getenv("JWT_SECRET")
constants.JWT_TYPE = os.Getenv("JWT_TYPE")
constants.AUTHORIZER_URL = strings.TrimSuffix(os.Getenv("AUTHORIZER_URL"), "/")
constants.PORT = os.Getenv("PORT")
constants.REDIS_URL = os.Getenv("REDIS_URL")
constants.COOKIE_NAME = os.Getenv("COOKIE_NAME")
constants.GOOGLE_CLIENT_ID = os.Getenv("GOOGLE_CLIENT_ID")
constants.GOOGLE_CLIENT_SECRET = os.Getenv("GOOGLE_CLIENT_SECRET")
constants.GITHUB_CLIENT_ID = os.Getenv("GITHUB_CLIENT_ID")
constants.GITHUB_CLIENT_SECRET = os.Getenv("GITHUB_CLIENT_SECRET")
constants.FACEBOOK_CLIENT_ID = os.Getenv("FACEBOOK_CLIENT_ID")
constants.FACEBOOK_CLIENT_SECRET = os.Getenv("FACEBOOK_CLIENT_SECRET")
constants.TWITTER_CLIENT_ID = os.Getenv("TWITTER_CLIENT_ID")
constants.TWITTER_CLIENT_SECRET = os.Getenv("TWITTER_CLIENT_SECRET")
constants.RESET_PASSWORD_URL = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/")
constants.DISABLE_BASIC_AUTHENTICATION = os.Getenv("DISABLE_BASIC_AUTHENTICATION") == "true"
constants.DISABLE_EMAIL_VERIFICATION = os.Getenv("DISABLE_EMAIL_VERIFICATION") == "true"
constants.DISABLE_MAGIC_LOGIN = os.Getenv("DISABLE_MAGIC_LOGIN") == "true"
constants.JWT_ROLE_CLAIM = os.Getenv("JWT_ROLE_CLAIM")
if constants.ADMIN_SECRET == "" {
panic("root admin secret is required")
constants.ADMIN_SECRET = os.Getenv("ADMIN_SECRET")
if constants.ADMIN_SECRET == "" {
panic("root admin secret is required")
}
}
if constants.ENV == "" {
constants.ENV = "production"
constants.ENV = os.Getenv("ENV")
if constants.ENV == "" {
constants.ENV = "production"
}
if constants.ENV == "production" {
constants.IS_PROD = true
os.Setenv("GIN_MODE", "release")
} else {
constants.IS_PROD = false
}
}
if constants.ENV == "production" {
constants.IS_PROD = true
os.Setenv("GIN_MODE", "release")
} else {
constants.IS_PROD = false
if constants.DATABASE_TYPE == "" {
constants.DATABASE_TYPE = os.Getenv("DATABASE_TYPE")
log.Println(constants.DATABASE_TYPE)
if ARG_DB_TYPE != nil && *ARG_DB_TYPE != "" {
constants.DATABASE_TYPE = *ARG_DB_TYPE
}
if constants.DATABASE_TYPE == "" {
panic("DATABASE_TYPE is required")
}
}
if constants.DATABASE_URL == "" {
constants.DATABASE_URL = os.Getenv("DATABASE_URL")
if ARG_DB_URL != nil && *ARG_DB_URL != "" {
constants.DATABASE_URL = *ARG_DB_URL
}
if constants.DATABASE_URL == "" {
panic("DATABASE_URL is required")
}
}
if constants.DATABASE_NAME == "" {
constants.DATABASE_NAME = os.Getenv("DATABASE_NAME")
if constants.DATABASE_NAME == "" {
constants.DATABASE_NAME = "authorizer"
}
}
if constants.SMTP_HOST == "" {
constants.SMTP_HOST = os.Getenv("SMTP_HOST")
}
if constants.SMTP_PORT == "" {
constants.SMTP_PORT = os.Getenv("SMTP_PORT")
}
if constants.SMTP_USERNAME == "" {
constants.SMTP_USERNAME = os.Getenv("SMTP_USERNAME")
}
if constants.SMTP_PASSWORD == "" {
constants.SMTP_PASSWORD = os.Getenv("SMTP_PASSWORD")
}
if constants.SENDER_EMAIL == "" {
constants.SENDER_EMAIL = os.Getenv("SENDER_EMAIL")
}
if constants.JWT_SECRET == "" {
constants.JWT_SECRET = os.Getenv("JWT_SECRET")
}
if constants.JWT_TYPE == "" {
constants.JWT_TYPE = os.Getenv("JWT_TYPE")
}
if constants.JWT_ROLE_CLAIM == "" {
constants.JWT_ROLE_CLAIM = os.Getenv("JWT_ROLE_CLAIM")
if constants.JWT_ROLE_CLAIM == "" {
constants.JWT_ROLE_CLAIM = "role"
}
}
if constants.AUTHORIZER_URL == "" {
constants.AUTHORIZER_URL = strings.TrimSuffix(os.Getenv("AUTHORIZER_URL"), "/")
if ARG_AUTHORIZER_URL != nil && *ARG_AUTHORIZER_URL != "" {
constants.AUTHORIZER_URL = *ARG_AUTHORIZER_URL
}
}
if constants.PORT == "" {
constants.PORT = os.Getenv("PORT")
if constants.PORT == "" {
constants.PORT = "8080"
}
}
if constants.REDIS_URL == "" {
constants.REDIS_URL = os.Getenv("REDIS_URL")
}
if constants.COOKIE_NAME == "" {
constants.COOKIE_NAME = os.Getenv("COOKIE_NAME")
}
if constants.GOOGLE_CLIENT_ID == "" {
constants.GOOGLE_CLIENT_ID = os.Getenv("GOOGLE_CLIENT_ID")
}
if constants.GOOGLE_CLIENT_SECRET == "" {
constants.GOOGLE_CLIENT_SECRET = os.Getenv("GOOGLE_CLIENT_SECRET")
}
if constants.GITHUB_CLIENT_ID == "" {
constants.GITHUB_CLIENT_ID = os.Getenv("GITHUB_CLIENT_ID")
}
if constants.GITHUB_CLIENT_SECRET == "" {
constants.GITHUB_CLIENT_SECRET = os.Getenv("GITHUB_CLIENT_SECRET")
}
if constants.FACEBOOK_CLIENT_ID == "" {
constants.FACEBOOK_CLIENT_ID = os.Getenv("FACEBOOK_CLIENT_ID")
}
if constants.FACEBOOK_CLIENT_SECRET == "" {
constants.FACEBOOK_CLIENT_SECRET = os.Getenv("FACEBOOK_CLIENT_SECRET")
}
if constants.RESET_PASSWORD_URL == "" {
constants.RESET_PASSWORD_URL = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/")
}
constants.DISABLE_BASIC_AUTHENTICATION = os.Getenv("DISABLE_BASIC_AUTHENTICATION") == "true"
constants.DISABLE_EMAIL_VERIFICATION = os.Getenv("DISABLE_EMAIL_VERIFICATION") == "true"
constants.DISABLE_MAGIC_LINK_LOGIN = os.Getenv("DISABLE_MAGIC_LINK_LOGIN") == "true"
constants.DISABLE_LOGIN_PAGE = os.Getenv("DISABLE_LOGIN_PAGE") == "true"
if constants.SMTP_HOST == "" || constants.SMTP_USERNAME == "" || constants.SMTP_PASSWORD == "" || constants.SENDER_EMAIL == "" {
constants.DISABLE_EMAIL_VERIFICATION = true
constants.DISABLE_MAGIC_LINK_LOGIN = true
}
allowedOriginsSplit := strings.Split(os.Getenv("ALLOWED_ORIGINS"), ",")
@@ -113,30 +211,6 @@ func InitEnv() {
constants.ALLOWED_ORIGINS = allowedOrigins
if *ARG_AUTHORIZER_URL != "" {
constants.AUTHORIZER_URL = *ARG_AUTHORIZER_URL
}
if *ARG_DB_URL != "" {
constants.DATABASE_URL = *ARG_DB_URL
}
if *ARG_DB_TYPE != "" {
constants.DATABASE_TYPE = *ARG_DB_TYPE
}
if constants.DATABASE_URL == "" {
panic("Database url is required")
}
if constants.DATABASE_TYPE == "" {
panic("Database type is required")
}
if constants.DATABASE_NAME == "" {
constants.DATABASE_NAME = "authorizer"
}
if constants.JWT_TYPE == "" {
constants.JWT_TYPE = "HS256"
}
@@ -145,32 +219,30 @@ func InitEnv() {
constants.COOKIE_NAME = "authorizer"
}
if constants.SMTP_HOST == "" || constants.SENDER_EMAIL == "" || constants.SENDER_PASSWORD == "" {
constants.DISABLE_EMAIL_VERIFICATION = true
constants.DISABLE_MAGIC_LOGIN = true
}
if constants.DISABLE_EMAIL_VERIFICATION {
constants.DISABLE_MAGIC_LOGIN = true
constants.DISABLE_MAGIC_LINK_LOGIN = true
}
rolesSplit := strings.Split(os.Getenv("ROLES"), ",")
rolesEnv := strings.TrimSpace(os.Getenv("ROLES"))
rolesSplit := strings.Split(rolesEnv, ",")
roles := []string{}
if len(rolesSplit) == 0 {
if len(rolesEnv) == 0 {
roles = []string{"user"}
}
defaultRoleSplit := strings.Split(os.Getenv("DEFAULT_ROLES"), ",")
defaultRolesEnv := strings.TrimSpace(os.Getenv("DEFAULT_ROLES"))
defaultRoleSplit := strings.Split(defaultRolesEnv, ",")
defaultRoles := []string{}
if len(defaultRoleSplit) == 0 {
if len(defaultRolesEnv) == 0 {
defaultRoles = []string{"user"}
}
protectedRolesSplit := strings.Split(os.Getenv("PROTECTED_ROLES"), ",")
protectedRolesEnv := strings.TrimSpace(os.Getenv("PROTECTED_ROLES"))
protectedRolesSplit := strings.Split(protectedRolesEnv, ",")
protectedRoles := []string{}
if len(protectedRolesSplit) > 0 {
if len(protectedRolesEnv) > 0 {
for _, val := range protectedRolesSplit {
trimVal := strings.TrimSpace(val)
protectedRoles = append(protectedRoles, trimVal)
@@ -196,10 +268,6 @@ func InitEnv() {
constants.DEFAULT_ROLES = defaultRoles
constants.PROTECTED_ROLES = protectedRoles
if constants.JWT_ROLE_CLAIM == "" {
constants.JWT_ROLE_CLAIM = "role"
}
if os.Getenv("ORGANIZATION_NAME") != "" {
constants.ORGANIZATION_NAME = os.Getenv("ORGANIZATION_NAME")
}

View File

@@ -3,7 +3,7 @@ module github.com/authorizerdev/authorizer/server
go 1.16
require (
github.com/99designs/gqlgen v0.13.0
github.com/99designs/gqlgen v0.14.0
github.com/arangodb/go-driver v1.2.1
github.com/coreos/go-oidc/v3 v3.1.0
github.com/gin-contrib/location v0.0.2
@@ -20,15 +20,17 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f
github.com/stretchr/testify v1.7.0 // indirect
github.com/stretchr/testify v1.7.0
github.com/ugorji/go v1.2.6 // indirect
github.com/vektah/gqlparser/v2 v2.1.0
go.mongodb.org/mongo-driver v1.8.1 // indirect
github.com/vektah/gqlparser/v2 v2.2.0
go.mongodb.org/mongo-driver v1.8.1
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/mail.v2 v2.3.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gorm.io/driver/mysql v1.2.1
gorm.io/driver/postgres v1.2.3

View File

@@ -31,15 +31,15 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/99designs/gqlgen v0.13.0 h1:haLTcUp3Vwp80xMVEg5KRNwzfUrgFdRmtBY8fuB8scA=
github.com/99designs/gqlgen v0.13.0/go.mod h1:NV130r6f4tpRWuAI+zsrSdooO/eWUv+Gyyoi3rEfXIk=
github.com/99designs/gqlgen v0.14.0 h1:Wg8aNYQUjMR/4v+W3xD+7SizOy6lSvVeQ06AobNQAXI=
github.com/99designs/gqlgen v0.14.0/go.mod h1:S7z4boV+Nx4VvzMUpVrY/YuHjFX4n7rDyuTqvAkuoRE=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM=
github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/arangodb/go-driver v1.2.1 h1:HREDHhDmzdIWxHmfkfTESbYUnRjESjPh4WUuXq7FZa8=
@@ -73,8 +73,8 @@ github.com/denisenkom/go-mssqldb v0.11.0 h1:9rHa233rhdOyrz2GcP9NM+gi2psgJZ4GWDpL
github.com/denisenkom/go-mssqldb v0.11.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c h1:TUuUh0Xgj97tLMNtWtNvI9mIV6isjEb9lBMNv+77IGM=
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@@ -89,7 +89,6 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA=
github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -113,7 +112,7 @@ github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
@@ -246,6 +245,7 @@ github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMW
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
@@ -328,7 +328,6 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -338,6 +337,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
@@ -347,8 +347,8 @@ github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
github.com/vektah/gqlparser/v2 v2.1.0 h1:uiKJ+T5HMGGQM2kRKQ8Pxw8+Zq9qhhZhz/lieYvCMns=
github.com/vektah/gqlparser/v2 v2.1.0/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
github.com/vektah/gqlparser/v2 v2.2.0 h1:bAc3slekAAJW6sZTi07aGq0OrfaCjj4jxARAaC7g2EM=
github.com/vektah/gqlparser/v2 v2.2.0/go.mod h1:i3mQIGIrbK2PD1RrCeMTlVbkF2FJ6WkU1KJlJlC+3F4=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w=
@@ -565,7 +565,6 @@ golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@@ -581,10 +580,12 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -671,6 +672,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
@@ -678,6 +681,8 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbRxng+XOj5ITVgY24WcbNnQopyfKoYQ=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
@@ -715,5 +720,5 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=

File diff suppressed because it is too large Load Diff

View File

@@ -2,20 +2,11 @@
package model
type AdminUpdateUserInput struct {
ID string `json:"id"`
Email *string `json:"email"`
FirstName *string `json:"firstName"`
LastName *string `json:"lastName"`
Image *string `json:"image"`
Roles []*string `json:"roles"`
}
type AuthResponse struct {
Message string `json:"message"`
AccessToken *string `json:"accessToken"`
AccessTokenExpiresAt *int64 `json:"accessTokenExpiresAt"`
User *User `json:"user"`
Message string `json:"message"`
AccessToken *string `json:"access_token"`
ExpiresAt *int64 `json:"expires_at"`
User *User `json:"user"`
}
type DeleteUserInput struct {
@@ -37,30 +28,30 @@ type LoginInput struct {
Roles []string `json:"roles"`
}
type MagicLoginInput struct {
type MagicLinkLoginInput struct {
Email string `json:"email"`
Roles []string `json:"roles"`
}
type Meta struct {
Version string `json:"version"`
IsGoogleLoginEnabled bool `json:"isGoogleLoginEnabled"`
IsFacebookLoginEnabled bool `json:"isFacebookLoginEnabled"`
IsTwitterLoginEnabled bool `json:"isTwitterLoginEnabled"`
IsGithubLoginEnabled bool `json:"isGithubLoginEnabled"`
IsEmailVerificationEnabled bool `json:"isEmailVerificationEnabled"`
IsBasicAuthenticationEnabled bool `json:"isBasicAuthenticationEnabled"`
IsMagicLoginEnabled bool `json:"isMagicLoginEnabled"`
IsGoogleLoginEnabled bool `json:"is_google_login_enabled"`
IsFacebookLoginEnabled bool `json:"is_facebook_login_enabled"`
IsGithubLoginEnabled bool `json:"is_github_login_enabled"`
IsEmailVerificationEnabled bool `json:"is_email_verification_enabled"`
IsBasicAuthenticationEnabled bool `json:"is_basic_authentication_enabled"`
IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"`
}
type ResendVerifyEmailInput struct {
Email string `json:"email"`
Email string `json:"email"`
Identifier string `json:"identifier"`
}
type ResetPasswordInput struct {
Token string `json:"token"`
Password string `json:"password"`
ConfirmPassword string `json:"confirmPassword"`
ConfirmPassword string `json:"confirm_password"`
}
type Response struct {
@@ -68,36 +59,67 @@ type Response struct {
}
type SignUpInput struct {
FirstName *string `json:"firstName"`
LastName *string `json:"lastName"`
Email string `json:"email"`
GivenName *string `json:"given_name"`
FamilyName *string `json:"family_name"`
MiddleName *string `json:"middle_name"`
Nickname *string `json:"nickname"`
Gender *string `json:"gender"`
Birthdate *string `json:"birthdate"`
PhoneNumber *string `json:"phone_number"`
Picture *string `json:"picture"`
Password string `json:"password"`
ConfirmPassword string `json:"confirmPassword"`
Image *string `json:"image"`
ConfirmPassword string `json:"confirm_password"`
Roles []string `json:"roles"`
}
type UpdateProfileInput struct {
OldPassword *string `json:"oldPassword"`
NewPassword *string `json:"newPassword"`
ConfirmNewPassword *string `json:"confirmNewPassword"`
FirstName *string `json:"firstName"`
LastName *string `json:"lastName"`
Image *string `json:"image"`
OldPassword *string `json:"old_password"`
NewPassword *string `json:"new_password"`
ConfirmNewPassword *string `json:"confirm_new_password"`
Email *string `json:"email"`
GivenName *string `json:"given_name"`
FamilyName *string `json:"family_name"`
MiddleName *string `json:"middle_name"`
Nickname *string `json:"nickname"`
Gender *string `json:"gender"`
Birthdate *string `json:"birthdate"`
PhoneNumber *string `json:"phone_number"`
Picture *string `json:"picture"`
}
type UpdateUserInput struct {
ID string `json:"id"`
Email *string `json:"email"`
GivenName *string `json:"given_name"`
FamilyName *string `json:"family_name"`
MiddleName *string `json:"middle_name"`
Nickname *string `json:"nickname"`
Gender *string `json:"gender"`
Birthdate *string `json:"birthdate"`
PhoneNumber *string `json:"phone_number"`
Picture *string `json:"picture"`
Roles []*string `json:"roles"`
}
type User struct {
ID string `json:"id"`
Email string `json:"email"`
SignupMethod string `json:"signupMethod"`
FirstName *string `json:"firstName"`
LastName *string `json:"lastName"`
EmailVerifiedAt *int64 `json:"emailVerifiedAt"`
Image *string `json:"image"`
CreatedAt *int64 `json:"createdAt"`
UpdatedAt *int64 `json:"updatedAt"`
Roles []string `json:"roles"`
ID string `json:"id"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
SignupMethods string `json:"signup_methods"`
GivenName *string `json:"given_name"`
FamilyName *string `json:"family_name"`
MiddleName *string `json:"middle_name"`
Nickname *string `json:"nickname"`
PreferredUsername *string `json:"preferred_username"`
Gender *string `json:"gender"`
Birthdate *string `json:"birthdate"`
PhoneNumber *string `json:"phone_number"`
PhoneNumberVerified *bool `json:"phone_number_verified"`
Picture *string `json:"picture"`
Roles []string `json:"roles"`
CreatedAt *int64 `json:"created_at"`
UpdatedAt *int64 `json:"updated_at"`
}
type VerificationRequest struct {
@@ -106,8 +128,8 @@ type VerificationRequest struct {
Token *string `json:"token"`
Email *string `json:"email"`
Expires *int64 `json:"expires"`
CreatedAt *int64 `json:"createdAt"`
UpdatedAt *int64 `json:"updatedAt"`
CreatedAt *int64 `json:"created_at"`
UpdatedAt *int64 `json:"updated_at"`
}
type VerifyEmailInput struct {

View File

@@ -7,26 +7,33 @@ scalar Any
type Meta {
version: String!
isGoogleLoginEnabled: Boolean!
isFacebookLoginEnabled: Boolean!
isTwitterLoginEnabled: Boolean!
isGithubLoginEnabled: Boolean!
isEmailVerificationEnabled: Boolean!
isBasicAuthenticationEnabled: Boolean!
isMagicLoginEnabled: Boolean!
is_google_login_enabled: Boolean!
is_facebook_login_enabled: Boolean!
is_github_login_enabled: Boolean!
is_email_verification_enabled: Boolean!
is_basic_authentication_enabled: Boolean!
is_magic_link_login_enabled: Boolean!
}
type User {
id: ID!
email: String!
signupMethod: String!
firstName: String
lastName: String
emailVerifiedAt: Int64
image: String
createdAt: Int64
updatedAt: Int64
email_verified: Boolean!
signup_methods: String!
given_name: String
family_name: String
middle_name: String
nickname: String
# defaults to email
preferred_username: String
gender: String
birthdate: String
phone_number: String
phone_number_verified: Boolean
picture: String
roles: [String!]!
created_at: Int64
updated_at: Int64
}
type VerificationRequest {
@@ -35,8 +42,8 @@ type VerificationRequest {
token: String
email: String
expires: Int64
createdAt: Int64
updatedAt: Int64
created_at: Int64
updated_at: Int64
}
type Error {
@@ -46,8 +53,8 @@ type Error {
type AuthResponse {
message: String!
accessToken: String
accessTokenExpiresAt: Int64
access_token: String
expires_at: Int64
user: User
}
@@ -56,12 +63,17 @@ type Response {
}
input SignUpInput {
firstName: String
lastName: String
email: String!
given_name: String
family_name: String
middle_name: String
nickname: String
gender: String
birthdate: String
phone_number: String
picture: String
password: String!
confirmPassword: String!
image: String
confirm_password: String!
roles: [String!]
}
@@ -77,25 +89,35 @@ input VerifyEmailInput {
input ResendVerifyEmailInput {
email: String!
identifier: String!
}
input UpdateProfileInput {
oldPassword: String
newPassword: String
confirmNewPassword: String
firstName: String
lastName: String
image: String
old_password: String
new_password: String
confirm_new_password: String
email: String
# roles: [String]
given_name: String
family_name: String
middle_name: String
nickname: String
gender: String
birthdate: String
phone_number: String
picture: String
}
input AdminUpdateUserInput {
input UpdateUserInput {
id: ID!
email: String
firstName: String
lastName: String
image: String
given_name: String
family_name: String
middle_name: String
nickname: String
gender: String
birthdate: String
phone_number: String
picture: String
roles: [String]
}
@@ -106,14 +128,14 @@ input ForgotPasswordInput {
input ResetPasswordInput {
token: String!
password: String!
confirmPassword: String!
confirm_password: String!
}
input DeleteUserInput {
email: String!
}
input MagicLoginInput {
input MagicLinkLoginInput {
email: String!
roles: [String!]
}
@@ -121,21 +143,23 @@ input MagicLoginInput {
type Mutation {
signup(params: SignUpInput!): AuthResponse!
login(params: LoginInput!): AuthResponse!
magicLogin(params: MagicLoginInput!): Response!
magic_link_login(params: MagicLinkLoginInput!): Response!
logout: Response!
updateProfile(params: UpdateProfileInput!): Response!
adminUpdateUser(params: AdminUpdateUserInput!): User!
verifyEmail(params: VerifyEmailInput!): AuthResponse!
resendVerifyEmail(params: ResendVerifyEmailInput!): Response!
forgotPassword(params: ForgotPasswordInput!): Response!
resetPassword(params: ResetPasswordInput!): Response!
deleteUser(params: DeleteUserInput!): Response!
update_profile(params: UpdateProfileInput!): Response!
verify_email(params: VerifyEmailInput!): AuthResponse!
resend_verify_email(params: ResendVerifyEmailInput!): Response!
forgot_password(params: ForgotPasswordInput!): Response!
reset_password(params: ResetPasswordInput!): Response!
# admin only apis
_delete_user(params: DeleteUserInput!): Response!
_update_user(params: UpdateUserInput!): User!
}
type Query {
meta: Meta!
users: [User!]!
token(roles: [String!]): AuthResponse
session(roles: [String!]): AuthResponse
profile: User!
verificationRequests: [VerificationRequest!]!
# admin only apis
_users: [User!]!
_verification_requests: [VerificationRequest!]!
}

View File

@@ -19,8 +19,8 @@ func (r *mutationResolver) Login(ctx context.Context, params model.LoginInput) (
return resolvers.Login(ctx, params)
}
func (r *mutationResolver) MagicLogin(ctx context.Context, params model.MagicLoginInput) (*model.Response, error) {
return resolvers.MagicLogin(ctx, params)
func (r *mutationResolver) MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) {
return resolvers.MagicLinkLogin(ctx, params)
}
func (r *mutationResolver) Logout(ctx context.Context) (*model.Response, error) {
@@ -31,10 +31,6 @@ func (r *mutationResolver) UpdateProfile(ctx context.Context, params model.Updat
return resolvers.UpdateProfile(ctx, params)
}
func (r *mutationResolver) AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*model.User, error) {
return resolvers.AdminUpdateUser(ctx, params)
}
func (r *mutationResolver) VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.AuthResponse, error) {
return resolvers.VerifyEmail(ctx, params)
}
@@ -55,22 +51,26 @@ func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUs
return resolvers.DeleteUser(ctx, params)
}
func (r *mutationResolver) UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error) {
return resolvers.UpdateUser(ctx, params)
}
func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) {
return resolvers.Meta(ctx)
}
func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
return resolvers.Users(ctx)
}
func (r *queryResolver) Token(ctx context.Context, roles []string) (*model.AuthResponse, error) {
return resolvers.Token(ctx, roles)
func (r *queryResolver) Session(ctx context.Context, roles []string) (*model.AuthResponse, error) {
return resolvers.Session(ctx, roles)
}
func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) {
return resolvers.Profile(ctx)
}
func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
return resolvers.Users(ctx)
}
func (r *queryResolver) VerificationRequests(ctx context.Context) ([]*model.VerificationRequest, error) {
return resolvers.VerificationRequests(ctx)
}
@@ -81,5 +81,7 @@ func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResol
// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
type (
mutationResolver struct{ *Resolver }
queryResolver struct{ *Resolver }
)

View File

@@ -43,26 +43,10 @@ func processGoogleUserInfo(code string) (db.User, error) {
return user, fmt.Errorf("unable to verify id_token: %s", err.Error())
}
// Extract custom claims
var claims struct {
Email string `json:"email"`
Picture string `json:"picture"`
GivenName string `json:"given_name"`
FamilyName string `json:"family_name"`
Verified bool `json:"email_verified"`
}
if err := idToken.Claims(&claims); err != nil {
if err := idToken.Claims(&user); err != nil {
return user, fmt.Errorf("unable to extract claims")
}
user = db.User{
FirstName: claims.GivenName,
LastName: claims.FamilyName,
Image: claims.Picture,
Email: claims.Email,
EmailVerifiedAt: time.Now().Unix(),
}
return user, nil
}
@@ -104,12 +88,14 @@ func processGithubUserInfo(code string) (db.User, error) {
if len(name) > 1 && strings.TrimSpace(name[1]) != "" {
lastName = name[0]
}
picture := userRawData["avatar_url"]
user = db.User{
FirstName: firstName,
LastName: lastName,
Image: userRawData["avatar_url"],
Email: userRawData["email"],
EmailVerifiedAt: time.Now().Unix(),
GivenName: &firstName,
FamilyName: &lastName,
Picture: &picture,
Email: userRawData["email"],
}
return user, nil
@@ -146,12 +132,15 @@ func processFacebookUserInfo(code string) (db.User, error) {
picObject := userRawData["picture"].(map[string]interface{})["data"]
picDataObject := picObject.(map[string]interface{})
firstName := fmt.Sprintf("%v", userRawData["first_name"])
lastName := fmt.Sprintf("%v", userRawData["last_name"])
picture := fmt.Sprintf("%v", picDataObject["url"])
user = db.User{
FirstName: fmt.Sprintf("%v", userRawData["first_name"]),
LastName: fmt.Sprintf("%v", userRawData["last_name"]),
Image: fmt.Sprintf("%v", picDataObject["url"]),
Email: email,
EmailVerifiedAt: time.Now().Unix(),
GivenName: &firstName,
FamilyName: &lastName,
Picture: &picture,
Email: email,
}
return user, nil
@@ -202,7 +191,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
if err != nil {
// user not registered, register user and generate session token
user.SignupMethod = provider
user.SignupMethods = provider
// make sure inputRoles don't include protected roles
hasProtectedRole := false
for _, ir := range inputRoles {
@@ -217,16 +206,18 @@ func OAuthCallbackHandler() gin.HandlerFunc {
}
user.Roles = strings.Join(inputRoles, ",")
now := time.Now().Unix()
user.EmailVerifiedAt = &now
user, _ = db.Mgr.AddUser(user)
} else {
// user exists in db, check if method was google
// if not append google to existing signup method and save it
signupMethod := existingUser.SignupMethod
signupMethod := existingUser.SignupMethods
if !strings.Contains(signupMethod, provider) {
signupMethod = signupMethod + "," + provider
}
user.SignupMethod = signupMethod
user.SignupMethods = signupMethod
user.Password = existingUser.Password
// There multiple scenarios with roles here in social login
@@ -262,7 +253,6 @@ func OAuthCallbackHandler() gin.HandlerFunc {
user.Roles = existingUser.Roles
}
user.Key = existingUser.Key
user.ObjectID = existingUser.ObjectID
user.ID = existingUser.ID
user, err = db.Mgr.UpdateUser(user)
}
@@ -274,15 +264,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
accessToken, _, _ := utils.CreateAuthToken(user, enum.AccessToken, inputRoles)
utils.SetCookie(c, accessToken)
session.SetToken(userIdStr, accessToken, refreshToken)
go func() {
sessionData := db.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(c.Request),
IP: utils.GetIP(c.Request),
}
db.Mgr.AddSession(sessionData)
}()
utils.CreateSession(user.ID, c)
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
}

View File

@@ -1,7 +1,6 @@
package handlers
import (
"fmt"
"net/http"
"strings"
"time"
@@ -46,29 +45,21 @@ func VerifyEmailHandler() gin.HandlerFunc {
}
// update email_verified_at in users table
if user.EmailVerifiedAt <= 0 {
user.EmailVerifiedAt = time.Now().Unix()
if user.EmailVerifiedAt == nil {
now := time.Now().Unix()
user.EmailVerifiedAt = &now
db.Mgr.UpdateUser(user)
}
// delete from verification table
db.Mgr.DeleteVerificationRequest(verificationRequest)
userIdStr := fmt.Sprintf("%v", user.ID)
roles := strings.Split(user.Roles, ",")
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, roles)
accessToken, _, _ := utils.CreateAuthToken(user, enum.AccessToken, roles)
session.SetToken(userIdStr, accessToken, refreshToken)
go func() {
sessionData := db.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(c.Request),
IP: utils.GetIP(c.Request),
}
db.Mgr.AddSession(sessionData)
}()
session.SetToken(user.ID, accessToken, refreshToken)
utils.CreateSession(user.ID, c)
utils.SetCookie(c, accessToken)
c.Redirect(http.StatusTemporaryRedirect, claim.RedirectURL)
}

View File

@@ -1,44 +0,0 @@
package integration_test
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/env"
"github.com/authorizerdev/authorizer/server/middlewares"
"github.com/gin-contrib/location"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func TestCors(t *testing.T) {
constants.ENV_PATH = "../../.env.local"
env.InitEnv()
r := gin.Default()
r.Use(location.Default())
r.Use(middlewares.GinContextToContextMiddleware())
r.Use(middlewares.CORSMiddleware())
allowedOrigin := "http://localhost:8080" // The allowed origin that you want to check
notAllowedOrigin := "http://myapp.com"
server := httptest.NewServer(r)
defer server.Close()
client := &http.Client{}
req, _ := http.NewRequest(
"GET",
"http://"+server.Listener.Addr().String()+"/api",
nil,
)
req.Header.Add("Origin", allowedOrigin)
get, _ := client.Do(req)
// You should get your origin (or a * depending on your config) if the
// passed origin is allowed.
o := get.Header.Get("Access-Control-Allow-Origin")
assert.NotEqual(t, o, notAllowedOrigin, "Origins should not match")
assert.Equal(t, o, allowedOrigin, "Origins don't match")
}

View File

@@ -1,44 +1,48 @@
package main
import (
"flag"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/env"
"github.com/authorizerdev/authorizer/server/handlers"
"github.com/authorizerdev/authorizer/server/middlewares"
"github.com/authorizerdev/authorizer/server/oauth"
"github.com/authorizerdev/authorizer/server/router"
"github.com/authorizerdev/authorizer/server/session"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-contrib/location"
"github.com/gin-gonic/gin"
)
var VERSION string
func main() {
env.ARG_DB_URL = flag.String("database_url", "", "Database connection string")
env.ARG_DB_TYPE = flag.String("database_type", "", "Database type, possible values are postgres,mysql,sqlite")
env.ARG_AUTHORIZER_URL = flag.String("authorizer_url", "", "URL for authorizer instance, eg: https://xyz.herokuapp.com")
env.ARG_ENV_FILE = flag.String("env_file", "", "Env file path")
flag.Parse()
constants.VERSION = VERSION
env.InitEnv()
db.InitDB()
session.InitSession()
oauth.InitOAuth()
utils.InitServer()
r := gin.Default()
r.Use(location.Default())
r.Use(middlewares.GinContextToContextMiddleware())
r.Use(middlewares.CORSMiddleware())
router := router.InitRouter()
r.GET("/", handlers.PlaygroundHandler())
r.POST("/graphql", handlers.GraphqlHandler())
r.GET("/verify_email", handlers.VerifyEmailHandler())
r.GET("/oauth_login/:oauth_provider", handlers.OAuthLoginHandler())
r.GET("/oauth_callback/:oauth_provider", handlers.OAuthCallbackHandler())
// login wall app related routes
r.LoadHTMLGlob("templates/*")
app := r.Group("/app")
{
app.Static("/build", "app/build")
app.GET("/", handlers.AppHandler())
app.GET("/reset-password", handlers.AppHandler())
// login page app related routes.
// if we put them in router file then tests would fail as templates or build path will be different
if !constants.DISABLE_LOGIN_PAGE {
router.LoadHTMLGlob("templates/*")
app := router.Group("/app")
{
app.Static("/build", "app/build")
app.GET("/", handlers.AppHandler())
app.GET("/reset-password", handlers.AppHandler())
}
}
r.Run()
router.Run(":" + constants.PORT)
}

View File

@@ -14,7 +14,7 @@ func GinContextToContextMiddleware() gin.HandlerFunc {
if constants.AUTHORIZER_URL == "" {
url := location.Get(c)
constants.AUTHORIZER_URL = url.Scheme + "://" + c.Request.Host
log.Println("=> authorizer url:", constants.AUTHORIZER_URL)
log.Println("authorizer url:", constants.AUTHORIZER_URL)
}
ctx := context.WithValue(c.Request.Context(), "GinContextKey", c)
c.Request = c.Request.WithContext(ctx)

View File

@@ -32,15 +32,15 @@ func Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, e
return res, fmt.Errorf(`user with this email not found`)
}
if !strings.Contains(user.SignupMethod, enum.BasicAuth.String()) {
if !strings.Contains(user.SignupMethods, enum.BasicAuth.String()) {
return res, fmt.Errorf(`user has not signed up email & password`)
}
if user.EmailVerifiedAt <= 0 {
if user.EmailVerifiedAt == nil {
return res, fmt.Errorf(`email not verified`)
}
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(params.Password))
err = bcrypt.CompareHashAndPassword([]byte(*user.Password), []byte(params.Password))
if err != nil {
log.Println("compare password error:", err)
@@ -55,38 +55,18 @@ func Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, e
roles = params.Roles
}
userIdStr := fmt.Sprintf("%v", user.ID)
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, roles)
accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, roles)
session.SetToken(userIdStr, accessToken, refreshToken)
go func() {
sessionData := db.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request),
IP: utils.GetIP(gc.Request),
}
db.Mgr.AddSession(sessionData)
}()
session.SetToken(user.ID, accessToken, refreshToken)
utils.CreateSession(user.ID, gc)
res = &model.AuthResponse{
Message: `Logged in successfully`,
AccessToken: &accessToken,
AccessTokenExpiresAt: &expiresAt,
User: &model.User{
ID: userIdStr,
Email: user.Email,
Image: &user.Image,
FirstName: &user.FirstName,
LastName: &user.LastName,
SignupMethod: user.SignupMethod,
EmailVerifiedAt: &user.EmailVerifiedAt,
Roles: strings.Split(user.Roles, ","),
CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt,
},
Message: `Logged in successfully`,
AccessToken: &accessToken,
ExpiresAt: &expiresAt,
User: utils.GetResponseUserData(user),
}
utils.SetCookie(gc, accessToken)

View File

@@ -14,10 +14,10 @@ import (
"github.com/authorizerdev/authorizer/server/utils"
)
func MagicLogin(ctx context.Context, params model.MagicLoginInput) (*model.Response, error) {
func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) {
var res *model.Response
if constants.DISABLE_MAGIC_LOGIN {
if constants.DISABLE_MAGIC_LINK_LOGIN {
return res, fmt.Errorf(`magic link login is disabled for this instance`)
}
@@ -37,7 +37,7 @@ func MagicLogin(ctx context.Context, params model.MagicLoginInput) (*model.Respo
existingUser, err := db.Mgr.GetUserByEmail(params.Email)
if err != nil {
user.SignupMethod = enum.MagicLink.String()
user.SignupMethods = enum.MagicLinkLogin.String()
// define roles for new user
if len(params.Roles) > 0 {
// check if roles exists
@@ -86,12 +86,12 @@ func MagicLogin(ctx context.Context, params model.MagicLoginInput) (*model.Respo
user.Roles = existingUser.Roles
}
signupMethod := existingUser.SignupMethod
if !strings.Contains(signupMethod, enum.MagicLink.String()) {
signupMethod = signupMethod + "," + enum.MagicLink.String()
signupMethod := existingUser.SignupMethods
if !strings.Contains(signupMethod, enum.MagicLinkLogin.String()) {
signupMethod = signupMethod + "," + enum.MagicLinkLogin.String()
}
user.SignupMethod = signupMethod
user.SignupMethods = signupMethod
user, _ = db.Mgr.UpdateUser(user)
if err != nil {
log.Println("error updating user:", err)
@@ -100,7 +100,7 @@ func MagicLogin(ctx context.Context, params model.MagicLoginInput) (*model.Respo
if !constants.DISABLE_EMAIL_VERIFICATION {
// insert verification request
verificationType := enum.MagicLink.String()
verificationType := enum.MagicLinkLogin.String()
token, err := utils.CreateVerificationToken(params.Email, verificationType)
if err != nil {
log.Println(`error generating token`, err)

View File

@@ -3,7 +3,6 @@ package resolvers
import (
"context"
"fmt"
"strings"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
@@ -41,20 +40,7 @@ func Profile(ctx context.Context) (*model.User, error) {
return res, err
}
userIdStr := fmt.Sprintf("%v", user.ID)
res = &model.User{
ID: userIdStr,
Email: user.Email,
Image: &user.Image,
FirstName: &user.FirstName,
LastName: &user.LastName,
SignupMethod: user.SignupMethod,
EmailVerifiedAt: &user.EmailVerifiedAt,
Roles: strings.Split(user.Roles, ","),
CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt,
}
res = utils.GetResponseUserData(user)
return res, nil
}

View File

@@ -20,18 +20,28 @@ func ResendVerifyEmail(ctx context.Context, params model.ResendVerifyEmailInput)
return res, fmt.Errorf("invalid email")
}
verificationRequest, err := db.Mgr.GetVerificationByEmail(params.Email)
if !utils.IsValidVerificationIdentifier(params.Identifier) {
return res, fmt.Errorf("invalid identifier")
}
verificationRequest, err := db.Mgr.GetVerificationByEmail(params.Email, params.Identifier)
if err != nil {
return res, fmt.Errorf(`verification request not found`)
}
token, err := utils.CreateVerificationToken(params.Email, verificationRequest.Identifier)
// delete current verification and create new one
err = db.Mgr.DeleteVerificationRequest(verificationRequest)
if err != nil {
log.Println("error deleting verification request:", err)
}
token, err := utils.CreateVerificationToken(params.Email, params.Identifier)
if err != nil {
log.Println(`error generating token`, err)
}
db.Mgr.AddVerification(db.VerificationRequest{
Token: token,
Identifier: verificationRequest.Identifier,
Identifier: params.Identifier,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: params.Email,
})

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"strings"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
@@ -39,13 +40,19 @@ func ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model
}
password, _ := utils.HashPassword(params.Password)
user.Password = password
user.Password = &password
signupMethod := user.SignupMethod
signupMethod := user.SignupMethods
if !strings.Contains(signupMethod, enum.BasicAuth.String()) {
signupMethod = signupMethod + "," + enum.BasicAuth.String()
}
user.SignupMethod = signupMethod
user.SignupMethods = signupMethod
// helpful if user has not signed up with basic auth
if user.EmailVerifiedAt == nil {
now := time.Now().Unix()
user.EmailVerifiedAt = &now
}
// delete from verification table
db.Mgr.DeleteVerificationRequest(verificationRequest)

View File

@@ -3,7 +3,6 @@ package resolvers
import (
"context"
"fmt"
"strings"
"time"
"github.com/authorizerdev/authorizer/server/constants"
@@ -14,7 +13,7 @@ import (
"github.com/authorizerdev/authorizer/server/utils"
)
func Token(ctx context.Context, roles []string) (*model.AuthResponse, error) {
func Session(ctx context.Context, roles []string) (*model.AuthResponse, error) {
var res *model.AuthResponse
gc, err := utils.GinContextFromContext(ctx)
@@ -67,34 +66,15 @@ func Token(ctx context.Context, roles []string) (*model.AuthResponse, error) {
session.DeleteVerificationRequest(userIdStr, token)
token, expiresAt, _ = utils.CreateAuthToken(user, enum.AccessToken, claimRoles)
session.SetToken(userIdStr, token, currentRefreshToken)
go func() {
sessionData := db.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request),
IP: utils.GetIP(gc.Request),
}
db.Mgr.AddSession(sessionData)
}()
utils.CreateSession(user.ID, gc)
}
utils.SetCookie(gc, token)
res = &model.AuthResponse{
Message: `Token verified`,
AccessToken: &token,
AccessTokenExpiresAt: &expiresAt,
User: &model.User{
ID: userIdStr,
Email: user.Email,
Image: &user.Image,
FirstName: &user.FirstName,
LastName: &user.LastName,
Roles: strings.Split(user.Roles, ","),
CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt,
SignupMethod: user.SignupMethod,
EmailVerifiedAt: &user.EmailVerifiedAt,
},
Message: `Token verified`,
AccessToken: &token,
ExpiresAt: &expiresAt,
User: utils.GetResponseUserData(user),
}
return res, nil
}

View File

@@ -35,6 +35,19 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
return res, fmt.Errorf(`invalid email address`)
}
// find user with email
existingUser, err := db.Mgr.GetUserByEmail(params.Email)
if err != nil {
log.Println("user with email " + params.Email + " not found")
}
if existingUser.EmailVerifiedAt != nil {
// email is verified
return res, fmt.Errorf(`%s has already signed up`, params.Email)
} else if existingUser.ID != "" && existingUser.EmailVerifiedAt == nil {
return res, fmt.Errorf("%s has already signed up. please complete the email verification process or reset the password", params.Email)
}
inputRoles := []string{}
if len(params.Roles) > 0 {
@@ -48,19 +61,6 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
inputRoles = constants.DEFAULT_ROLES
}
// find user with email
existingUser, err := db.Mgr.GetUserByEmail(params.Email)
if err != nil {
log.Println("user with email " + params.Email + " not found")
}
if existingUser.EmailVerifiedAt > 0 {
// email is verified
return res, fmt.Errorf(`%s has already signed up`, params.Email)
} else if existingUser.ID != "" && existingUser.EmailVerifiedAt <= 0 {
return res, fmt.Errorf("%s has already signed up. please complete the email verification process or reset the password", params.Email)
}
user := db.User{
Email: params.Email,
}
@@ -68,19 +68,44 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
user.Roles = strings.Join(inputRoles, ",")
password, _ := utils.HashPassword(params.Password)
user.Password = password
user.Password = &password
if params.FirstName != nil {
user.FirstName = *params.FirstName
if params.GivenName != nil {
user.GivenName = params.GivenName
}
if params.LastName != nil {
user.LastName = *params.LastName
if params.FamilyName != nil {
user.FamilyName = params.FamilyName
}
user.SignupMethod = enum.BasicAuth.String()
if params.MiddleName != nil {
user.MiddleName = params.MiddleName
}
if params.Nickname != nil {
user.Nickname = params.Nickname
}
if params.Gender != nil {
user.Gender = params.Gender
}
if params.Birthdate != nil {
user.Birthdate = params.Birthdate
}
if params.PhoneNumber != nil {
user.PhoneNumber = params.PhoneNumber
}
if params.Picture != nil {
user.Picture = params.Picture
}
user.SignupMethods = enum.BasicAuth.String()
if constants.DISABLE_EMAIL_VERIFICATION {
user.EmailVerifiedAt = time.Now().Unix()
now := time.Now().Unix()
user.EmailVerifiedAt = &now
}
user, err = db.Mgr.AddUser(user)
if err != nil {
@@ -88,18 +113,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
}
userIdStr := fmt.Sprintf("%v", user.ID)
roles := strings.Split(user.Roles, ",")
userToReturn := &model.User{
ID: userIdStr,
Email: user.Email,
Image: &user.Image,
FirstName: &user.FirstName,
LastName: &user.LastName,
SignupMethod: user.SignupMethod,
EmailVerifiedAt: &user.EmailVerifiedAt,
Roles: strings.Split(user.Roles, ","),
CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt,
}
userToReturn := utils.GetResponseUserData(user)
if !constants.DISABLE_EMAIL_VERIFICATION {
// insert verification request
@@ -131,20 +145,12 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, roles)
session.SetToken(userIdStr, accessToken, refreshToken)
go func() {
sessionData := db.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request),
IP: utils.GetIP(gc.Request),
}
db.Mgr.AddSession(sessionData)
}()
utils.CreateSession(user.ID, gc)
res = &model.AuthResponse{
Message: `Signed up successfully.`,
AccessToken: &accessToken,
AccessTokenExpiresAt: &expiresAt,
User: userToReturn,
Message: `Signed up successfully.`,
AccessToken: &accessToken,
ExpiresAt: &expiresAt,
User: userToReturn,
}
utils.SetCookie(gc, accessToken)

View File

@@ -40,7 +40,7 @@ func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model
}
// validate if all params are not empty
if params.FirstName == nil && params.LastName == nil && params.Image == nil && params.OldPassword == nil && params.Email == nil {
if params.GivenName == nil && params.FamilyName == nil && params.Picture == nil && params.MiddleName == nil && params.Nickname == nil && params.OldPassword == nil && params.Email == nil && params.Birthdate == nil && params.Gender == nil && params.PhoneNumber == nil {
return res, fmt.Errorf("please enter atleast one param to update")
}
@@ -50,20 +50,40 @@ func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model
return res, err
}
if params.FirstName != nil && user.FirstName != *params.FirstName {
user.FirstName = *params.FirstName
if params.GivenName != nil && user.GivenName != params.GivenName {
user.GivenName = params.GivenName
}
if params.LastName != nil && user.LastName != *params.LastName {
user.LastName = *params.LastName
if params.FamilyName != nil && user.FamilyName != params.FamilyName {
user.FamilyName = params.FamilyName
}
if params.Image != nil && user.Image != *params.Image {
user.Image = *params.Image
if params.MiddleName != nil && user.MiddleName != params.MiddleName {
user.MiddleName = params.MiddleName
}
if params.Nickname != nil && user.Nickname != params.Nickname {
user.Nickname = params.Nickname
}
if params.Birthdate != nil && user.Birthdate != params.Birthdate {
user.Birthdate = params.Birthdate
}
if params.Gender != nil && user.Gender != params.Gender {
user.Gender = params.Gender
}
if params.PhoneNumber != nil && user.PhoneNumber != params.PhoneNumber {
user.PhoneNumber = params.PhoneNumber
}
if params.Picture != nil && user.Picture != params.Picture {
user.Picture = params.Picture
}
if params.OldPassword != nil {
if err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(*params.OldPassword)); err != nil {
if err = bcrypt.CompareHashAndPassword([]byte(*user.Password), []byte(*params.OldPassword)); err != nil {
return res, fmt.Errorf("incorrect old password")
}
@@ -81,7 +101,7 @@ func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model
password, _ := utils.HashPassword(*params.NewPassword)
user.Password = password
user.Password = &password
}
hasEmailChanged := false
@@ -93,7 +113,8 @@ func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model
}
newEmail := strings.ToLower(*params.Email)
// check if user with new email exists
_, err = db.Mgr.GetUserByEmail(newEmail)
_, err := db.Mgr.GetUserByEmail(newEmail)
// err = nil means user exists
if err == nil {
return res, fmt.Errorf("user with this email address already exists")
@@ -103,7 +124,7 @@ func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model
utils.DeleteCookie(gc)
user.Email = newEmail
user.EmailVerifiedAt = 0
user.EmailVerifiedAt = nil
hasEmailChanged = true
// insert verification request
verificationType := enum.UpdateEmail.String()

View File

@@ -15,7 +15,7 @@ import (
"github.com/authorizerdev/authorizer/server/utils"
)
func AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*model.User, error) {
func UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error) {
gc, err := utils.GinContextFromContext(ctx)
var res *model.User
if err != nil {
@@ -26,7 +26,7 @@ func AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*m
return res, fmt.Errorf("unauthorized")
}
if params.FirstName == nil && params.LastName == nil && params.Image == nil && params.Email == nil && params.Roles == nil {
if params.GivenName == nil && params.FamilyName == nil && params.Picture == nil && params.MiddleName == nil && params.Nickname == nil && params.Email == nil && params.Birthdate == nil && params.Gender == nil && params.PhoneNumber == nil && params.Roles == nil {
return res, fmt.Errorf("please enter atleast one param to update")
}
@@ -35,16 +35,36 @@ func AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*m
return res, fmt.Errorf(`User not found`)
}
if params.FirstName != nil && user.FirstName != *params.FirstName {
user.FirstName = *params.FirstName
if params.GivenName != nil && user.GivenName != params.GivenName {
user.GivenName = params.GivenName
}
if params.LastName != nil && user.LastName != *params.LastName {
user.LastName = *params.LastName
if params.FamilyName != nil && user.FamilyName != params.FamilyName {
user.FamilyName = params.FamilyName
}
if params.Image != nil && user.Image != *params.Image {
user.Image = *params.Image
if params.MiddleName != nil && user.MiddleName != params.MiddleName {
user.MiddleName = params.MiddleName
}
if params.Nickname != nil && user.Nickname != params.Nickname {
user.Nickname = params.Nickname
}
if params.Birthdate != nil && user.Birthdate != params.Birthdate {
user.Birthdate = params.Birthdate
}
if params.Gender != nil && user.Gender != params.Gender {
user.Gender = params.Gender
}
if params.PhoneNumber != nil && user.PhoneNumber != params.PhoneNumber {
user.PhoneNumber = params.PhoneNumber
}
if params.Picture != nil && user.Picture != params.Picture {
user.Picture = params.Picture
}
if params.Email != nil && user.Email != *params.Email {
@@ -64,7 +84,7 @@ func AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*m
utils.DeleteCookie(gc)
user.Email = newEmail
user.EmailVerifiedAt = 0
user.EmailVerifiedAt = nil
// insert verification request
verificationType := enum.UpdateEmail.String()
token, err := utils.CreateVerificationToken(newEmail, verificationType)
@@ -115,14 +135,14 @@ func AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*m
}
res = &model.User{
ID: params.ID,
Email: user.Email,
Image: &user.Image,
FirstName: &user.FirstName,
LastName: &user.LastName,
Roles: strings.Split(user.Roles, ","),
CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt,
ID: params.ID,
Email: user.Email,
Picture: user.Picture,
GivenName: user.GivenName,
FamilyName: user.FamilyName,
Roles: strings.Split(user.Roles, ","),
CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt,
}
return res, nil
}

View File

@@ -3,7 +3,6 @@ package resolvers
import (
"context"
"fmt"
"strings"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
@@ -27,17 +26,7 @@ func Users(ctx context.Context) ([]*model.User, error) {
}
for i := 0; i < len(users); i++ {
res = append(res, &model.User{
ID: fmt.Sprintf("%v", users[i].ID),
Email: users[i].Email,
SignupMethod: users[i].SignupMethod,
FirstName: &users[i].FirstName,
LastName: &users[i].LastName,
EmailVerifiedAt: &users[i].EmailVerifiedAt,
Roles: strings.Split(users[i].Roles, ","),
CreatedAt: &users[i].CreatedAt,
UpdatedAt: &users[i].UpdatedAt,
})
res = append(res, utils.GetResponseUserData(users[i]))
}
return res, nil

View File

@@ -37,44 +37,25 @@ func VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.Aut
}
// update email_verified_at in users table
user.EmailVerifiedAt = time.Now().Unix()
now := time.Now().Unix()
user.EmailVerifiedAt = &now
db.Mgr.UpdateUser(user)
// delete from verification table
db.Mgr.DeleteVerificationRequest(verificationRequest)
userIdStr := fmt.Sprintf("%v", user.ID)
roles := strings.Split(user.Roles, ",")
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, roles)
accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, roles)
session.SetToken(userIdStr, accessToken, refreshToken)
go func() {
sessionData := db.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request),
IP: utils.GetIP(gc.Request),
}
db.Mgr.AddSession(sessionData)
}()
session.SetToken(user.ID, accessToken, refreshToken)
utils.CreateSession(user.ID, gc)
res = &model.AuthResponse{
Message: `Email verified successfully.`,
AccessToken: &accessToken,
AccessTokenExpiresAt: &expiresAt,
User: &model.User{
ID: userIdStr,
Email: user.Email,
Image: &user.Image,
FirstName: &user.FirstName,
LastName: &user.LastName,
SignupMethod: user.SignupMethod,
EmailVerifiedAt: &user.EmailVerifiedAt,
Roles: strings.Split(user.Roles, ","),
CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt,
},
Message: `Email verified successfully.`,
AccessToken: &accessToken,
ExpiresAt: &expiresAt,
User: utils.GetResponseUserData(user),
}
utils.SetCookie(gc, accessToken)

23
server/router/router.go Normal file
View File

@@ -0,0 +1,23 @@
package router
import (
"github.com/authorizerdev/authorizer/server/handlers"
"github.com/authorizerdev/authorizer/server/middlewares"
"github.com/gin-contrib/location"
"github.com/gin-gonic/gin"
)
func InitRouter() *gin.Engine {
router := gin.Default()
router.Use(location.Default())
router.Use(middlewares.GinContextToContextMiddleware())
router.Use(middlewares.CORSMiddleware())
router.GET("/", handlers.PlaygroundHandler())
router.POST("/graphql", handlers.GraphqlHandler())
router.GET("/verify_email", handlers.VerifyEmailHandler())
router.GET("/oauth_login/:oauth_provider", handlers.OAuthLoginHandler())
router.GET("/oauth_callback/:oauth_provider", handlers.OAuthCallbackHandler())
return router
}

View File

@@ -26,34 +26,31 @@ func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (st
expiresAt := time.Now().Add(expiryBound).Unix()
resUser := GetResponseUserData(user)
userBytes, _ := json.Marshal(&resUser)
var userMap map[string]interface{}
json.Unmarshal(userBytes, &userMap)
customClaims := jwt.MapClaims{
"exp": expiresAt,
"iat": time.Now().Unix(),
"token_type": tokenType.String(),
"email": user.Email,
"id": user.ID,
"allowed_roles": strings.Split(user.Roles, ","),
constants.JWT_ROLE_CLAIM: roles,
}
// check for the extra access token script
for k, v := range userMap {
if k != "roles" {
customClaims[k] = v
}
}
// check for the extra access token script
accessTokenScript := os.Getenv("CUSTOM_ACCESS_TOKEN_SCRIPT")
if accessTokenScript != "" {
userInfo := map[string]interface{}{
"id": user.ID,
"email": user.Email,
"firstName": user.FirstName,
"lastName": user.LastName,
"image": user.Image,
"roles": strings.Split(user.Roles, ","),
"signUpMethods": strings.Split(user.SignupMethod, ","),
}
vm := otto.New()
userBytes, _ := json.Marshal(userInfo)
claimBytes, _ := json.Marshal(customClaims)
claimBytes, _ := json.Marshal(customClaims)
vm.Run(fmt.Sprintf(`
var user = %s;
var tokenPayload = %s;

View File

@@ -0,0 +1,16 @@
package utils
import (
"github.com/authorizerdev/authorizer/server/db"
"github.com/gin-gonic/gin"
)
func CreateSession(userId string, c *gin.Context) {
sessionData := db.Session{
UserID: userId,
UserAgent: GetUserAgent(c.Request),
IP: GetIP(c.Request),
}
db.Mgr.AddSession(sessionData)
}

View File

@@ -1,7 +1,9 @@
package utils
import (
"fmt"
"bytes"
"encoding/json"
"html/template"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/email"
@@ -9,16 +11,13 @@ import (
// SendVerificationMail to send verification email
func SendVerificationMail(toEmail, token string) error {
sender := email.NewSender()
// The receiver needs to be in slice as the receive supports multiple receiver
Receiver := []string{toEmail}
Subject := "Please verify your email"
message := fmt.Sprintf(`
message := `
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1" name="viewport">
@@ -41,7 +40,6 @@ func SendVerificationMail(toEmail, token string) error {
</xml>
<![endif]-->
</head>
<body style="font-family: sans-serif;">
<div class="es-wrapper-color">
<!--[if gte mso 9]>
@@ -68,14 +66,15 @@ func SendVerificationMail(toEmail, token string) error {
<table width="100%%" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank"><img src="%s" alt="icon" style="display: block;" title="icon" width="30"></a></td>
<td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank" clicktracking="off"><img src="{{.OrgLogo}}" alt="icon" style="display: block;" title="icon" width="30"></a></td>
</tr>
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;">
<td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
<p>Hey there 👋</p>
<p>We received a request to sign-up / login for <b>%s</b>. If this is correct, please confirm your email address by clicking the button below.</p> <br/>
<a href="%s" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Confirm Email</a>
<p>We have received request to verify email for <b>{{.OrgName}}</b>. If this is correct, please confirm your email address by clicking the button below.</p> <br/>
<a
clicktracking="off" href="{{.AuthUrl}}" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Confirm Email</a>
</td>
</tr>
</tbody>
@@ -100,10 +99,15 @@ func SendVerificationMail(toEmail, token string) error {
<div style="position: absolute; left: -9999px; top: -9999px; margin: 0px;"></div>
</body>
</html>
`, constants.ORGANIZATION_LOGO, constants.ORGANIZATION_NAME, constants.AUTHORIZER_URL+"/verify_email"+"?token="+token)
bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
`
data := make(map[string]interface{}, 3)
data["OrgLogo"] = constants.ORGANIZATION_LOGO
data["OrgName"] = constants.ORGANIZATION_NAME
data["AuthUrl"] = constants.AUTHORIZER_URL + "/verify_email?token=" + token
message = AddEmailTemplate(message, data, "verify_email.tmpl")
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
return sender.SendMail(Receiver, Subject, bodyMessage)
return email.SendMail(Receiver, Subject, message)
}
// SendForgotPasswordMail to send verification email
@@ -112,17 +116,14 @@ func SendForgotPasswordMail(toEmail, token, host string) error {
constants.RESET_PASSWORD_URL = constants.AUTHORIZER_URL + "/app/reset-password"
}
sender := email.NewSender()
// The receiver needs to be in slice as the receive supports multiple receiver
Receiver := []string{toEmail}
Subject := "Reset Password"
message := fmt.Sprintf(`
message := `
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1" name="viewport">
@@ -145,7 +146,6 @@ func SendForgotPasswordMail(toEmail, token, host string) error {
</xml>
<![endif]-->
</head>
<body style="font-family: sans-serif;">
<div class="es-wrapper-color">
<!--[if gte mso 9]>
@@ -172,14 +172,14 @@ func SendForgotPasswordMail(toEmail, token, host string) error {
<table width="100%%" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank"><img src="%s" alt="icon" style="display: block;" title="icon" width="30"></a></td>
<td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank" clicktracking="off"><img src="{{.OrgLogo}}" alt="icon" style="display: block;" title="icon" width="30"></a></td>
</tr>
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;">
<td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
<p>Hey there 👋</p>
<p>We received a request to reset password for email: <b>%s</b>. If this is correct, please reset the password clicking the button below.</p> <br/>
<a href="%s" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Reset Password</a>
<p>We have received a request to reset password for email: <b>{{.ToEmail}}</b>. If this is correct, please reset the password clicking the button below.</p> <br/>
<a clicktracking="off" href="{{.AuthUrl}}" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Reset Password</a>
</td>
</tr>
</tbody>
@@ -204,9 +204,28 @@ func SendForgotPasswordMail(toEmail, token, host string) error {
<div style="position: absolute; left: -9999px; top: -9999px; margin: 0px;"></div>
</body>
</html>
`, constants.ORGANIZATION_LOGO, toEmail, constants.RESET_PASSWORD_URL+"?token="+token)
`
bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
data := make(map[string]interface{}, 3)
data["OrgLogo"] = constants.ORGANIZATION_LOGO
data["ToEmail"] = constants.ORGANIZATION_NAME
data["AuthUrl"] = constants.RESET_PASSWORD_URL + "?token=" + token
message = AddEmailTemplate(message, data, "reset_password_email.tmpl")
return sender.SendMail(Receiver, Subject, bodyMessage)
return email.SendMail(Receiver, Subject, message)
}
func AddEmailTemplate(a string, b map[string]interface{}, templateName string) string {
tmpl, err := template.New(templateName).Parse(a)
if err != nil {
output, _ := json.Marshal(b)
return string(output)
}
buf := &bytes.Buffer{}
err = tmpl.Execute(buf, b)
if err != nil {
panic(err)
}
s := buf.String()
return s
}

View File

@@ -0,0 +1,32 @@
package utils
import (
"strings"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
)
func GetResponseUserData(user db.User) *model.User {
isEmailVerified := user.EmailVerifiedAt != nil
isPhoneVerified := user.PhoneNumberVerifiedAt != nil
return &model.User{
ID: user.ID,
Email: user.Email,
EmailVerified: isEmailVerified,
SignupMethods: user.SignupMethods,
GivenName: user.GivenName,
FamilyName: user.FamilyName,
MiddleName: user.MiddleName,
Nickname: user.Nickname,
PreferredUsername: &user.Email,
Gender: user.Gender,
Birthdate: user.Birthdate,
PhoneNumber: user.PhoneNumber,
PhoneNumberVerified: &isPhoneVerified,
Picture: user.Picture,
Roles: strings.Split(user.Roles, ","),
CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt,
}
}

View File

@@ -11,11 +11,10 @@ func GetMetaInfo() model.Meta {
return model.Meta{
Version: constants.VERSION,
IsGoogleLoginEnabled: constants.GOOGLE_CLIENT_ID != "" && constants.GOOGLE_CLIENT_SECRET != "",
IsGithubLoginEnabled: constants.GITHUB_CLIENT_ID != "" && constants.GOOGLE_CLIENT_SECRET != "",
IsGithubLoginEnabled: constants.GITHUB_CLIENT_ID != "" && constants.GITHUB_CLIENT_SECRET != "",
IsFacebookLoginEnabled: constants.FACEBOOK_CLIENT_ID != "" && constants.FACEBOOK_CLIENT_SECRET != "",
IsTwitterLoginEnabled: constants.TWITTER_CLIENT_ID != "" && constants.TWITTER_CLIENT_SECRET != "",
IsBasicAuthenticationEnabled: !constants.DISABLE_BASIC_AUTHENTICATION,
IsEmailVerificationEnabled: !constants.DISABLE_EMAIL_VERIFICATION,
IsMagicLoginEnabled: !constants.DISABLE_MAGIC_LOGIN,
IsMagicLinkLoginEnabled: !constants.DISABLE_MAGIC_LINK_LOGIN,
}
}

View File

@@ -6,6 +6,7 @@ import (
"strings"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/gin-gonic/gin"
)
@@ -69,6 +70,13 @@ func IsValidRoles(userRoles []string, roles []string) bool {
return valid
}
func IsValidVerificationIdentifier(identifier string) bool {
if identifier != enum.BasicAuthSignup.String() && identifier != enum.ForgotPassword.String() && identifier != enum.UpdateEmail.String() {
return false
}
return true
}
func IsStringArrayEqual(a, b []string) bool {
if len(a) != len(b) {
return false

View File

@@ -1,34 +0,0 @@
package utils
import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/stretchr/testify/assert"
)
func TestIsValidEmail(t *testing.T) {
validEmail := "lakhan@gmail.com"
invalidEmail1 := "lakhan"
invalidEmail2 := "lakhan.me"
assert.True(t, IsValidEmail(validEmail), "it should be valid email")
assert.False(t, IsValidEmail(invalidEmail1), "it should be invalid email")
assert.False(t, IsValidEmail(invalidEmail2), "it should be invalid email")
}
func TestIsValidOrigin(t *testing.T) {
// don't use portocal(http/https) for ALLOWED_ORIGINS while testing,
// as we trim them off while running the main function
constants.ALLOWED_ORIGINS = []string{"localhost:8080", "*.google.com", "*.google.in", "*abc.*"}
assert.False(t, IsValidOrigin("http://myapp.com"), "it should be invalid origin")
assert.False(t, IsValidOrigin("http://appgoogle.com"), "it should be invalid origin")
assert.True(t, IsValidOrigin("http://app.google.com"), "it should be valid origin")
assert.False(t, IsValidOrigin("http://app.google.ind"), "it should be invalid origin")
assert.True(t, IsValidOrigin("http://app.google.in"), "it should be valid origin")
assert.True(t, IsValidOrigin("http://xyx.abc.com"), "it should be valid origin")
assert.True(t, IsValidOrigin("http://xyx.abc.in"), "it should be valid origin")
assert.True(t, IsValidOrigin("http://xyxabc.in"), "it should be valid origin")
assert.True(t, IsValidOrigin("http://localhost:8080"), "it should be valid origin")
}

View File

@@ -24,7 +24,6 @@ func CreateVerificationToken(email string, tokenType string) (string, error) {
t.Claims = &CustomClaim{
&jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
},
tokenType,

View File

@@ -5,13 +5,13 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link rel="stylesheet" href="/app/build/bundle.css">
<link rel="stylesheet" href="/app/build/index.css">
<script>
window.__authorizer__ = {{.data}}
</script>
</head>
<body>
<div id="root"></div>
<script src="/app/build/bundle.js"></script>
<script type="module" src="/app/build/index.js"></script>
</body>
</html>
</html>