Merge pull request #270 from authorizerdev/fix/oauth-provider
fix(server): authorizer openid flow
This commit is contained in:
commit
f9d2130c65
10
.github/workflows/release.yaml
vendored
10
.github/workflows/release.yaml
vendored
|
@ -2,16 +2,16 @@ on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
logLevel:
|
logLevel:
|
||||||
description: "Log level"
|
description: 'Log level'
|
||||||
required: true
|
required: true
|
||||||
default: "warning"
|
default: 'warning'
|
||||||
type: choice
|
type: choice
|
||||||
options:
|
options:
|
||||||
- info
|
- info
|
||||||
- warning
|
- warning
|
||||||
- debug
|
- debug
|
||||||
tags:
|
tags:
|
||||||
description: "Tags"
|
description: 'Tags'
|
||||||
required: false
|
required: false
|
||||||
type: boolean
|
type: boolean
|
||||||
release:
|
release:
|
||||||
|
@ -25,7 +25,7 @@ jobs:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v2
|
- uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: "16"
|
node-version: '16'
|
||||||
- # Add support for more platforms with QEMU (optional)
|
- # Add support for more platforms with QEMU (optional)
|
||||||
# https://github.com/docker/setup-qemu-action
|
# https://github.com/docker/setup-qemu-action
|
||||||
name: Set up QEMU
|
name: Set up QEMU
|
||||||
|
@ -36,7 +36,7 @@ jobs:
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: "^1.17.3"
|
go-version: '^1.19.1'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install build-essential wget zip libc6-dev-arm64-cross && \
|
sudo apt-get install build-essential wget zip libc6-dev-arm64-cross && \
|
||||||
|
|
|
@ -2,5 +2,5 @@
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "all",
|
"trailingComma": "all",
|
||||||
"useTabs": false
|
"useTabs": true
|
||||||
}
|
}
|
||||||
|
|
30
app/package-lock.json
generated
30
app/package-lock.json
generated
|
@ -9,7 +9,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-react": "^1.1.2",
|
"@authorizerdev/authorizer-react": "^1.1.3-beta.1",
|
||||||
"@types/react": "^17.0.15",
|
"@types/react": "^17.0.15",
|
||||||
"@types/react-dom": "^17.0.9",
|
"@types/react-dom": "^17.0.9",
|
||||||
"esbuild": "^0.12.17",
|
"esbuild": "^0.12.17",
|
||||||
|
@ -27,9 +27,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@authorizerdev/authorizer-js": {
|
"node_modules/@authorizerdev/authorizer-js": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.2-beta.1",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.2-beta.1.tgz",
|
||||||
"integrity": "sha512-MdEw1SjhIm7pXq20AscHSbnAta2PC3w7GNBY52/OzmlBXUGH3ooUQX/aszbYOse3FlhapcrGrRvg4sNM7faGAg==",
|
"integrity": "sha512-u+O2iB3tqF1HtdJ6LfBXL9iMycqlCCL3othBQkqitGP1ldhASWLJ2pcXZAcHgyoeczKdj2XKZKdIcWB3GYR0IQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-fetch": "^3.1.5"
|
"cross-fetch": "^3.1.5"
|
||||||
},
|
},
|
||||||
|
@ -38,11 +38,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@authorizerdev/authorizer-react": {
|
"node_modules/@authorizerdev/authorizer-react": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.3-beta.1",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.3-beta.1.tgz",
|
||||||
"integrity": "sha512-uBmuKnOVX8gp8CEUuGJuz04ep+8qMEzJXWd5leEGKYMIgolHpu/lOinnMUXhjh8YL3pA4+EhvB+hQXxUX+rRHQ==",
|
"integrity": "sha512-+ZsOBp6XjZVnDyeJCXgaqZ8xzFO7ygpHB6v2cblCKIA3wX5pg/Dsg1oumHGrSHIEK8No/GOtCjSx4Rv6/CweBQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-js": "^1.1.0",
|
"@authorizerdev/authorizer-js": "^1.1.2-beta.1",
|
||||||
"final-form": "^4.20.2",
|
"final-form": "^4.20.2",
|
||||||
"react-final-form": "^6.5.3",
|
"react-final-form": "^6.5.3",
|
||||||
"styled-components": "^5.3.0"
|
"styled-components": "^5.3.0"
|
||||||
|
@ -876,19 +876,19 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-js": {
|
"@authorizerdev/authorizer-js": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.2-beta.1",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-1.1.2-beta.1.tgz",
|
||||||
"integrity": "sha512-MdEw1SjhIm7pXq20AscHSbnAta2PC3w7GNBY52/OzmlBXUGH3ooUQX/aszbYOse3FlhapcrGrRvg4sNM7faGAg==",
|
"integrity": "sha512-u+O2iB3tqF1HtdJ6LfBXL9iMycqlCCL3othBQkqitGP1ldhASWLJ2pcXZAcHgyoeczKdj2XKZKdIcWB3GYR0IQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"cross-fetch": "^3.1.5"
|
"cross-fetch": "^3.1.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@authorizerdev/authorizer-react": {
|
"@authorizerdev/authorizer-react": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.3-beta.1",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-1.1.3-beta.1.tgz",
|
||||||
"integrity": "sha512-uBmuKnOVX8gp8CEUuGJuz04ep+8qMEzJXWd5leEGKYMIgolHpu/lOinnMUXhjh8YL3pA4+EhvB+hQXxUX+rRHQ==",
|
"integrity": "sha512-+ZsOBp6XjZVnDyeJCXgaqZ8xzFO7ygpHB6v2cblCKIA3wX5pg/Dsg1oumHGrSHIEK8No/GOtCjSx4Rv6/CweBQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@authorizerdev/authorizer-js": "^1.1.0",
|
"@authorizerdev/authorizer-js": "^1.1.2-beta.1",
|
||||||
"final-form": "^4.20.2",
|
"final-form": "^4.20.2",
|
||||||
"react-final-form": "^6.5.3",
|
"react-final-form": "^6.5.3",
|
||||||
"styled-components": "^5.3.0"
|
"styled-components": "^5.3.0"
|
||||||
|
|
|
@ -6,13 +6,13 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js",
|
"build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js",
|
||||||
"start": "NODE_ENV=development node ./esbuild.config.js",
|
"start": "NODE_ENV=development node ./esbuild.config.js",
|
||||||
"format": "prettier --write --use-tabs 'src/**/*.(ts|tsx|js|jsx)'"
|
"format": "prettier --write 'src/**/*.(ts|tsx|js|jsx)'"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Lakhan Samani",
|
"author": "Lakhan Samani",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-react": "^1.1.2",
|
"@authorizerdev/authorizer-react": "^1.1.3",
|
||||||
"@types/react": "^17.0.15",
|
"@types/react": "^17.0.15",
|
||||||
"@types/react-dom": "^17.0.9",
|
"@types/react-dom": "^17.0.9",
|
||||||
"esbuild": "^0.12.17",
|
"esbuild": "^0.12.17",
|
||||||
|
|
|
@ -38,6 +38,8 @@ export default function Root({
|
||||||
const scope = searchParams.get('scope')
|
const scope = searchParams.get('scope')
|
||||||
? searchParams.get('scope')?.toString().split(' ')
|
? searchParams.get('scope')?.toString().split(' ')
|
||||||
: ['openid', 'profile', 'email'];
|
: ['openid', 'profile', 'email'];
|
||||||
|
const code = searchParams.get('code') || '';
|
||||||
|
const nonce = searchParams.get('nonce') || '';
|
||||||
|
|
||||||
const urlProps: Record<string, any> = {
|
const urlProps: Record<string, any> = {
|
||||||
state,
|
state,
|
||||||
|
@ -58,9 +60,19 @@ export default function Root({
|
||||||
if (token) {
|
if (token) {
|
||||||
let redirectURL = config.redirectURL || '/app';
|
let redirectURL = config.redirectURL || '/app';
|
||||||
let params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&state=${globalState.state}`;
|
let params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&state=${globalState.state}`;
|
||||||
|
|
||||||
|
if (code !== '') {
|
||||||
|
params += `&code=${code}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nonce !== '') {
|
||||||
|
params += `&nonce=${nonce}`;
|
||||||
|
}
|
||||||
|
|
||||||
if (token.refresh_token) {
|
if (token.refresh_token) {
|
||||||
params += `&refresh_token=${token.refresh_token}`;
|
params += `&refresh_token=${token.refresh_token}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL(redirectURL);
|
const url = new URL(redirectURL);
|
||||||
if (redirectURL.includes('?')) {
|
if (redirectURL.includes('?')) {
|
||||||
redirectURL = `${redirectURL}&${params}`;
|
redirectURL = `${redirectURL}&${params}`;
|
||||||
|
@ -74,7 +86,7 @@ export default function Root({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return () => {};
|
return () => {};
|
||||||
}, [token]);
|
}, [token, config]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <h1>Loading...</h1>;
|
return <h1>Loading...</h1>;
|
||||||
|
@ -100,7 +112,7 @@ export default function Root({
|
||||||
<Route path="/app" exact>
|
<Route path="/app" exact>
|
||||||
<Login urlProps={urlProps} />
|
<Login urlProps={urlProps} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/app/signup" exact>
|
<Route path="/app/signup">
|
||||||
<SignUp urlProps={urlProps} />
|
<SignUp urlProps={urlProps} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/app/reset-password">
|
<Route path="/app/reset-password">
|
||||||
|
|
|
@ -2,5 +2,5 @@
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "all",
|
"trailingComma": "all",
|
||||||
"useTabs": false
|
"useTabs": true
|
||||||
}
|
}
|
||||||
|
|
19
server/constants/oauth2.go
Normal file
19
server/constants/oauth2.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package constants
|
||||||
|
|
||||||
|
const (
|
||||||
|
// - query: for Authorization Code grant. 302 Found triggers redirect.
|
||||||
|
ResponseModeQuery = "query"
|
||||||
|
// - fragment: for Implicit grant. 302 Found triggers redirect.
|
||||||
|
ResponseModeFragment = "fragment"
|
||||||
|
// - form_post: 200 OK with response parameters embedded in an HTML form as hidden parameters.
|
||||||
|
ResponseModeFormPost = "form_post"
|
||||||
|
// - web_message: For Silent Authentication. Uses HTML5 web messaging.
|
||||||
|
ResponseModeWebMessage = "web_message"
|
||||||
|
|
||||||
|
// For the Authorization Code grant, use response_type=code to include the authorization code.
|
||||||
|
ResponseTypeCode = "code"
|
||||||
|
// For the Implicit grant, use response_type=token to include an access token.
|
||||||
|
ResponseTypeToken = "token"
|
||||||
|
// For the Implicit grant of id_token, use response_type=id_token to include an identifier token.
|
||||||
|
ResponseTypeIDToken = "id_token"
|
||||||
|
)
|
|
@ -7,9 +7,8 @@ require (
|
||||||
github.com/arangodb/go-driver v1.2.1
|
github.com/arangodb/go-driver v1.2.1
|
||||||
github.com/aws/aws-sdk-go v1.44.109
|
github.com/aws/aws-sdk-go v1.44.109
|
||||||
github.com/coreos/go-oidc/v3 v3.1.0
|
github.com/coreos/go-oidc/v3 v3.1.0
|
||||||
github.com/denisenkom/go-mssqldb v0.11.0 // indirect
|
|
||||||
github.com/gin-gonic/gin v1.8.1
|
github.com/gin-gonic/gin v1.8.1
|
||||||
github.com/glebarez/sqlite v1.5.0 // indirect
|
github.com/glebarez/sqlite v1.5.0
|
||||||
github.com/go-playground/validator/v10 v10.11.1 // indirect
|
github.com/go-playground/validator/v10 v10.11.1 // indirect
|
||||||
github.com/go-redis/redis/v8 v8.11.0
|
github.com/go-redis/redis/v8 v8.11.0
|
||||||
github.com/goccy/go-json v0.9.11 // indirect
|
github.com/goccy/go-json v0.9.11 // indirect
|
||||||
|
@ -19,7 +18,6 @@ require (
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/guregu/dynamo v1.16.0
|
github.com/guregu/dynamo v1.16.0
|
||||||
github.com/joho/godotenv v1.3.0
|
github.com/joho/godotenv v1.3.0
|
||||||
github.com/mitchellh/gox v1.0.1 // indirect
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||||
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f
|
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f
|
||||||
|
|
|
@ -85,8 +85,6 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
|
github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
|
||||||
github.com/denisenkom/go-mssqldb v0.11.0 h1:9rHa233rhdOyrz2GcP9NM+gi2psgJZ4GWDpL/7ND8HI=
|
|
||||||
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 h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
|
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
|
||||||
|
@ -141,7 +139,6 @@ github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzq
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
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-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||||
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
|
||||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
|
@ -213,8 +210,6 @@ github.com/guregu/dynamo v1.16.0 h1:gmI8oi1VHwYQtq7+RPBeOiSssVLgxH/Az2t+NtDtL2c=
|
||||||
github.com/guregu/dynamo v1.16.0/go.mod h1:W2Gqcf3MtkrS+Q6fHPGAmRtT0Dyq+TGrqfqrUC9+R/c=
|
github.com/guregu/dynamo v1.16.0/go.mod h1:W2Gqcf3MtkrS+Q6fHPGAmRtT0Dyq+TGrqfqrUC9+R/c=
|
||||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
||||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||||
github.com/hashicorp/go-version v1.0.0 h1:21MVWPKDphxa7ineQQTrCU5brh7OuVVAzGOCnnCPtE8=
|
|
||||||
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||||
|
@ -232,8 +227,6 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU
|
||||||
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
|
||||||
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
|
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
|
||||||
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
||||||
github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8=
|
|
||||||
github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
|
|
||||||
github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
|
github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys=
|
||||||
github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
|
github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI=
|
||||||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
|
||||||
|
@ -252,8 +245,6 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW
|
||||||
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
|
||||||
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns=
|
|
||||||
github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
|
||||||
github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=
|
github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y=
|
||||||
github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
|
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
|
||||||
|
@ -262,28 +253,20 @@ github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01C
|
||||||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
|
||||||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
|
||||||
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
|
||||||
github.com/jackc/pgtype v1.9.0 h1:/SH1RxEtltvJgsDqp3TbiTFApD3mey3iygpuEGeuBXk=
|
|
||||||
github.com/jackc/pgtype v1.9.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
|
||||||
github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
|
github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w=
|
||||||
github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
|
||||||
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
|
||||||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
|
||||||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
|
||||||
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
|
||||||
github.com/jackc/pgx/v4 v4.14.0 h1:TgdrmgnM7VY72EuSQzBbBd4JA1RLqJolrw9nQVZABVc=
|
|
||||||
github.com/jackc/pgx/v4 v4.14.0/go.mod h1:jT3ibf/A0ZVCp89rtCIN0zCJxcE74ypROmHEZYsG/j8=
|
|
||||||
github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
|
github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E=
|
||||||
github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
|
github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw=
|
||||||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
|
||||||
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
|
||||||
github.com/jinzhu/now v1.1.3 h1:PlHq1bSCSZL9K0wUhbm2pGLoTWs2GwVhsP6emvGV/ZI=
|
|
||||||
github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
|
||||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
@ -332,16 +315,9 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE=
|
github.com/microsoft/go-mssqldb v0.17.0 h1:Fto83dMZPnYv1Zwx5vHHxpNraeEaUlQ/hhHLgZiaenE=
|
||||||
github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ=
|
github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ=
|
||||||
github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI=
|
|
||||||
github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4=
|
|
||||||
github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
|
|
||||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
|
||||||
github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA=
|
github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA=
|
||||||
github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
@ -457,7 +433,6 @@ go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
@ -472,8 +447,6 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A=
|
|
||||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
|
||||||
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0=
|
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0=
|
||||||
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
@ -814,26 +787,13 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/driver/mysql v1.2.1 h1:h+3f1l9Ng2C072Y2tIiLgPpWN78r1KXL7bHJ0nTjlhU=
|
|
||||||
gorm.io/driver/mysql v1.2.1/go.mod h1:qsiz+XcAyMrS6QY+X3M9R6b/lKM1imKmcuK9kac5LTo=
|
|
||||||
gorm.io/driver/mysql v1.4.3 h1:/JhWJhO2v17d8hjApTltKNADm7K7YI2ogkR7avJUL3k=
|
gorm.io/driver/mysql v1.4.3 h1:/JhWJhO2v17d8hjApTltKNADm7K7YI2ogkR7avJUL3k=
|
||||||
gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
|
gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c=
|
||||||
gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To=
|
|
||||||
gorm.io/driver/postgres v1.2.3/go.mod h1:pJV6RgYQPG47aM1f0QeOzFH9HxQc8JcmAgjRCgS0wjs=
|
|
||||||
gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc=
|
gorm.io/driver/postgres v1.4.5 h1:mTeXTTtHAgnS9PgmhN2YeUbazYpLhUI1doLnw42XUZc=
|
||||||
gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg=
|
gorm.io/driver/postgres v1.4.5/go.mod h1:GKNQYSJ14qvWkvPwXljMGehpKrhlDNsqYRr5HnYGncg=
|
||||||
gorm.io/driver/sqlite v1.2.6 h1:SStaH/b+280M7C8vXeZLz/zo9cLQmIGwwj3cSj7p6l4=
|
|
||||||
gorm.io/driver/sqlite v1.2.6/go.mod h1:gyoX0vHiiwi0g49tv+x2E7l8ksauLK0U/gShcdUsjWY=
|
|
||||||
gorm.io/driver/sqlserver v1.2.1 h1:KhGOjvPX7JZ5hPyQICTJfMuTz88zgJ2lk9bWiHVNHd8=
|
|
||||||
gorm.io/driver/sqlserver v1.2.1/go.mod h1:nixq0OB3iLXZDiPv6JSOjWuPgpyaRpOIIevYtA4Ulb4=
|
|
||||||
gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0=
|
gorm.io/driver/sqlserver v1.4.1 h1:t4r4r6Jam5E6ejqP7N82qAJIJAht27EGT41HyPfXRw0=
|
||||||
gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig=
|
gorm.io/driver/sqlserver v1.4.1/go.mod h1:DJ4P+MeZbc5rvY58PnmN1Lnyvb5gw5NPzGshHDnJLig=
|
||||||
gorm.io/gorm v1.22.2/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
|
||||||
gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
|
||||||
gorm.io/gorm v1.22.4 h1:8aPcyEJhY0MAt8aY6Dc524Pn+pO29K+ydu+e/cXSpQM=
|
|
||||||
gorm.io/gorm v1.22.4/go.mod h1:1aeVC+pe9ZmvKZban/gW4QPra7PRoTEssyc922qCAkk=
|
|
||||||
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
|
||||||
gorm.io/gorm v1.24.0 h1:j/CoiSm6xpRpmzbFJsQHYj+I8bGYWLXVHeYEyyKlF74=
|
|
||||||
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||||
gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
gorm.io/gorm v1.24.1-0.20221019064659-5dd2bb482755/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||||
gorm.io/gorm v1.24.1 h1:CgvzRniUdG67hBAzsxDGOAuq4Te1osVMYsa1eQbd4fs=
|
gorm.io/gorm v1.24.1 h1:CgvzRniUdG67hBAzsxDGOAuq4Te1osVMYsa1eQbd4fs=
|
||||||
|
|
|
@ -2250,6 +2250,10 @@ input SignUpInput {
|
||||||
scope: [String!]
|
scope: [String!]
|
||||||
redirect_uri: String
|
redirect_uri: String
|
||||||
is_multi_factor_auth_enabled: Boolean
|
is_multi_factor_auth_enabled: Boolean
|
||||||
|
# state is used for authorization code grant flow
|
||||||
|
# it is used to get code for an on-going auth process during login
|
||||||
|
# and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token
|
||||||
|
state: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input LoginInput {
|
input LoginInput {
|
||||||
|
@ -2257,15 +2261,27 @@ input LoginInput {
|
||||||
password: String!
|
password: String!
|
||||||
roles: [String!]
|
roles: [String!]
|
||||||
scope: [String!]
|
scope: [String!]
|
||||||
|
# state is used for authorization code grant flow
|
||||||
|
# it is used to get code for an on-going auth process during login
|
||||||
|
# and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token
|
||||||
|
state: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input VerifyEmailInput {
|
input VerifyEmailInput {
|
||||||
token: String!
|
token: String!
|
||||||
|
# state is used for authorization code grant flow
|
||||||
|
# it is used to get code for an on-going auth process during login
|
||||||
|
# and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token
|
||||||
|
state: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input ResendVerifyEmailInput {
|
input ResendVerifyEmailInput {
|
||||||
email: String!
|
email: String!
|
||||||
identifier: String!
|
identifier: String!
|
||||||
|
# state is used for authorization code grant flow
|
||||||
|
# it is used to get code for an on-going auth process during login
|
||||||
|
# and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token
|
||||||
|
state: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input UpdateProfileInput {
|
input UpdateProfileInput {
|
||||||
|
@ -2413,10 +2429,18 @@ input DeleteEmailTemplateRequest {
|
||||||
input VerifyOTPRequest {
|
input VerifyOTPRequest {
|
||||||
email: String!
|
email: String!
|
||||||
otp: String!
|
otp: String!
|
||||||
|
# state is used for authorization code grant flow
|
||||||
|
# it is used to get code for an on-going auth process during login
|
||||||
|
# and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token
|
||||||
|
state: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input ResendOTPRequest {
|
input ResendOTPRequest {
|
||||||
email: String!
|
email: String!
|
||||||
|
# state is used for authorization code grant flow
|
||||||
|
# it is used to get code for an on-going auth process during login
|
||||||
|
# and use that code for setting ` + "`" + `c_hash` + "`" + ` in id_token
|
||||||
|
state: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
|
@ -14455,7 +14479,7 @@ func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj in
|
||||||
asMap[k] = v
|
asMap[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldsInOrder := [...]string{"email", "password", "roles", "scope"}
|
fieldsInOrder := [...]string{"email", "password", "roles", "scope", "state"}
|
||||||
for _, k := range fieldsInOrder {
|
for _, k := range fieldsInOrder {
|
||||||
v, ok := asMap[k]
|
v, ok := asMap[k]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -14494,6 +14518,14 @@ func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj in
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return it, err
|
return it, err
|
||||||
}
|
}
|
||||||
|
case "state":
|
||||||
|
var err error
|
||||||
|
|
||||||
|
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("state"))
|
||||||
|
it.State, err = ec.unmarshalOString2ᚖstring(ctx, v)
|
||||||
|
if err != nil {
|
||||||
|
return it, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14659,7 +14691,7 @@ func (ec *executionContext) unmarshalInputResendOTPRequest(ctx context.Context,
|
||||||
asMap[k] = v
|
asMap[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldsInOrder := [...]string{"email"}
|
fieldsInOrder := [...]string{"email", "state"}
|
||||||
for _, k := range fieldsInOrder {
|
for _, k := range fieldsInOrder {
|
||||||
v, ok := asMap[k]
|
v, ok := asMap[k]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -14674,6 +14706,14 @@ func (ec *executionContext) unmarshalInputResendOTPRequest(ctx context.Context,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return it, err
|
return it, err
|
||||||
}
|
}
|
||||||
|
case "state":
|
||||||
|
var err error
|
||||||
|
|
||||||
|
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("state"))
|
||||||
|
it.State, err = ec.unmarshalOString2ᚖstring(ctx, v)
|
||||||
|
if err != nil {
|
||||||
|
return it, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14687,7 +14727,7 @@ func (ec *executionContext) unmarshalInputResendVerifyEmailInput(ctx context.Con
|
||||||
asMap[k] = v
|
asMap[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldsInOrder := [...]string{"email", "identifier"}
|
fieldsInOrder := [...]string{"email", "identifier", "state"}
|
||||||
for _, k := range fieldsInOrder {
|
for _, k := range fieldsInOrder {
|
||||||
v, ok := asMap[k]
|
v, ok := asMap[k]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -14710,6 +14750,14 @@ func (ec *executionContext) unmarshalInputResendVerifyEmailInput(ctx context.Con
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return it, err
|
return it, err
|
||||||
}
|
}
|
||||||
|
case "state":
|
||||||
|
var err error
|
||||||
|
|
||||||
|
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("state"))
|
||||||
|
it.State, err = ec.unmarshalOString2ᚖstring(ctx, v)
|
||||||
|
if err != nil {
|
||||||
|
return it, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14803,7 +14851,7 @@ func (ec *executionContext) unmarshalInputSignUpInput(ctx context.Context, obj i
|
||||||
asMap[k] = v
|
asMap[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldsInOrder := [...]string{"email", "given_name", "family_name", "middle_name", "nickname", "gender", "birthdate", "phone_number", "picture", "password", "confirm_password", "roles", "scope", "redirect_uri", "is_multi_factor_auth_enabled"}
|
fieldsInOrder := [...]string{"email", "given_name", "family_name", "middle_name", "nickname", "gender", "birthdate", "phone_number", "picture", "password", "confirm_password", "roles", "scope", "redirect_uri", "is_multi_factor_auth_enabled", "state"}
|
||||||
for _, k := range fieldsInOrder {
|
for _, k := range fieldsInOrder {
|
||||||
v, ok := asMap[k]
|
v, ok := asMap[k]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -14930,6 +14978,14 @@ func (ec *executionContext) unmarshalInputSignUpInput(ctx context.Context, obj i
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return it, err
|
return it, err
|
||||||
}
|
}
|
||||||
|
case "state":
|
||||||
|
var err error
|
||||||
|
|
||||||
|
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("state"))
|
||||||
|
it.State, err = ec.unmarshalOString2ᚖstring(ctx, v)
|
||||||
|
if err != nil {
|
||||||
|
return it, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15815,7 +15871,7 @@ func (ec *executionContext) unmarshalInputVerifyEmailInput(ctx context.Context,
|
||||||
asMap[k] = v
|
asMap[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldsInOrder := [...]string{"token"}
|
fieldsInOrder := [...]string{"token", "state"}
|
||||||
for _, k := range fieldsInOrder {
|
for _, k := range fieldsInOrder {
|
||||||
v, ok := asMap[k]
|
v, ok := asMap[k]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -15830,6 +15886,14 @@ func (ec *executionContext) unmarshalInputVerifyEmailInput(ctx context.Context,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return it, err
|
return it, err
|
||||||
}
|
}
|
||||||
|
case "state":
|
||||||
|
var err error
|
||||||
|
|
||||||
|
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("state"))
|
||||||
|
it.State, err = ec.unmarshalOString2ᚖstring(ctx, v)
|
||||||
|
if err != nil {
|
||||||
|
return it, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15843,7 +15907,7 @@ func (ec *executionContext) unmarshalInputVerifyOTPRequest(ctx context.Context,
|
||||||
asMap[k] = v
|
asMap[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldsInOrder := [...]string{"email", "otp"}
|
fieldsInOrder := [...]string{"email", "otp", "state"}
|
||||||
for _, k := range fieldsInOrder {
|
for _, k := range fieldsInOrder {
|
||||||
v, ok := asMap[k]
|
v, ok := asMap[k]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -15866,6 +15930,14 @@ func (ec *executionContext) unmarshalInputVerifyOTPRequest(ctx context.Context,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return it, err
|
return it, err
|
||||||
}
|
}
|
||||||
|
case "state":
|
||||||
|
var err error
|
||||||
|
|
||||||
|
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("state"))
|
||||||
|
it.State, err = ec.unmarshalOString2ᚖstring(ctx, v)
|
||||||
|
if err != nil {
|
||||||
|
return it, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -151,6 +151,7 @@ type LoginInput struct {
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
Roles []string `json:"roles"`
|
Roles []string `json:"roles"`
|
||||||
Scope []string `json:"scope"`
|
Scope []string `json:"scope"`
|
||||||
|
State *string `json:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MagicLinkLoginInput struct {
|
type MagicLinkLoginInput struct {
|
||||||
|
@ -200,11 +201,13 @@ type PaginationInput struct {
|
||||||
|
|
||||||
type ResendOTPRequest struct {
|
type ResendOTPRequest struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
State *string `json:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResendVerifyEmailInput struct {
|
type ResendVerifyEmailInput struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Identifier string `json:"identifier"`
|
Identifier string `json:"identifier"`
|
||||||
|
State *string `json:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResetPasswordInput struct {
|
type ResetPasswordInput struct {
|
||||||
|
@ -238,6 +241,7 @@ type SignUpInput struct {
|
||||||
Scope []string `json:"scope"`
|
Scope []string `json:"scope"`
|
||||||
RedirectURI *string `json:"redirect_uri"`
|
RedirectURI *string `json:"redirect_uri"`
|
||||||
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled"`
|
IsMultiFactorAuthEnabled *bool `json:"is_multi_factor_auth_enabled"`
|
||||||
|
State *string `json:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TestEndpointRequest struct {
|
type TestEndpointRequest struct {
|
||||||
|
@ -409,11 +413,13 @@ type VerificationRequests struct {
|
||||||
|
|
||||||
type VerifyEmailInput struct {
|
type VerifyEmailInput struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
|
State *string `json:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type VerifyOTPRequest struct {
|
type VerifyOTPRequest struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Otp string `json:"otp"`
|
Otp string `json:"otp"`
|
||||||
|
State *string `json:"state"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Webhook struct {
|
type Webhook struct {
|
||||||
|
|
|
@ -285,6 +285,10 @@ input SignUpInput {
|
||||||
scope: [String!]
|
scope: [String!]
|
||||||
redirect_uri: String
|
redirect_uri: String
|
||||||
is_multi_factor_auth_enabled: Boolean
|
is_multi_factor_auth_enabled: Boolean
|
||||||
|
# state is used for authorization code grant flow
|
||||||
|
# it is used to get code for an on-going auth process during login
|
||||||
|
# and use that code for setting `c_hash` in id_token
|
||||||
|
state: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input LoginInput {
|
input LoginInput {
|
||||||
|
@ -292,15 +296,27 @@ input LoginInput {
|
||||||
password: String!
|
password: String!
|
||||||
roles: [String!]
|
roles: [String!]
|
||||||
scope: [String!]
|
scope: [String!]
|
||||||
|
# state is used for authorization code grant flow
|
||||||
|
# it is used to get code for an on-going auth process during login
|
||||||
|
# and use that code for setting `c_hash` in id_token
|
||||||
|
state: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input VerifyEmailInput {
|
input VerifyEmailInput {
|
||||||
token: String!
|
token: String!
|
||||||
|
# state is used for authorization code grant flow
|
||||||
|
# it is used to get code for an on-going auth process during login
|
||||||
|
# and use that code for setting `c_hash` in id_token
|
||||||
|
state: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input ResendVerifyEmailInput {
|
input ResendVerifyEmailInput {
|
||||||
email: String!
|
email: String!
|
||||||
identifier: String!
|
identifier: String!
|
||||||
|
# state is used for authorization code grant flow
|
||||||
|
# it is used to get code for an on-going auth process during login
|
||||||
|
# and use that code for setting `c_hash` in id_token
|
||||||
|
state: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input UpdateProfileInput {
|
input UpdateProfileInput {
|
||||||
|
@ -430,6 +446,8 @@ input AddEmailTemplateRequest {
|
||||||
event_name: String!
|
event_name: String!
|
||||||
subject: String!
|
subject: String!
|
||||||
template: String!
|
template: String!
|
||||||
|
# Design value is set when editor is used
|
||||||
|
# If raw HTML is used design value is set to null
|
||||||
design: String
|
design: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,6 +456,8 @@ input UpdateEmailTemplateRequest {
|
||||||
event_name: String
|
event_name: String
|
||||||
template: String
|
template: String
|
||||||
subject: String
|
subject: String
|
||||||
|
# Design value is set when editor is used
|
||||||
|
# If raw HTML is used design value is set to null
|
||||||
design: String
|
design: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -448,10 +468,18 @@ input DeleteEmailTemplateRequest {
|
||||||
input VerifyOTPRequest {
|
input VerifyOTPRequest {
|
||||||
email: String!
|
email: String!
|
||||||
otp: String!
|
otp: String!
|
||||||
|
# state is used for authorization code grant flow
|
||||||
|
# it is used to get code for an on-going auth process during login
|
||||||
|
# and use that code for setting `c_hash` in id_token
|
||||||
|
state: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input ResendOTPRequest {
|
input ResendOTPRequest {
|
||||||
email: String!
|
email: String!
|
||||||
|
# state is used for authorization code grant flow
|
||||||
|
# it is used to get code for an on-going auth process during login
|
||||||
|
# and use that code for setting `c_hash` in id_token
|
||||||
|
state: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
|
|
|
@ -1,10 +1,41 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
|
/**
|
||||||
|
LOGIC TO REMEMBER THE AUTHORIZE FLOW
|
||||||
|
|
||||||
|
|
||||||
|
jargons
|
||||||
|
`at_hash` -> access_token_hash
|
||||||
|
`c_hash` -> code_hash
|
||||||
|
|
||||||
|
|
||||||
|
# ResponseType: Code
|
||||||
|
with /authorize request
|
||||||
|
- set state [state, code@@challenge]
|
||||||
|
- add &code to login redirect url
|
||||||
|
login resolver has optional param state
|
||||||
|
-if state found in store, split with @@
|
||||||
|
- if len > 1 -> response type is code and has code + challenge
|
||||||
|
- set `nonce, code` for createAuthToken request so that `c_hash` can be generated
|
||||||
|
- do not add `nonce` to id_token in code flow, instead set `c_hash` and `at_hash`
|
||||||
|
|
||||||
|
|
||||||
|
# ResponseType: token / id_token
|
||||||
|
with /authorize request
|
||||||
|
- set state [state, nonce]
|
||||||
|
- add &nonce to login redirect url
|
||||||
|
login resolver has optional param state
|
||||||
|
- if state found in store, split with @@
|
||||||
|
- if len < 1 -> response type is token / id_token and value is nonce
|
||||||
|
- send received nonce for createAuthToken with empty code value
|
||||||
|
- set `nonce` and `at_hash` in `id_token`
|
||||||
|
**/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
@ -17,6 +48,15 @@ import (
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Check the flow for generating and verifying codes: https://developer.okta.com/blog/2019/08/22/okta-authjs-pkce#:~:text=PKCE%20works%20by%20having%20the,is%20called%20the%20Code%20Challenge.
|
||||||
|
|
||||||
|
// Check following docs for understanding request / response params for various types of requests: https://auth0.com/docs/authenticate/login/oidc-conformant-authentication/oidc-adoption-auth-code-flow
|
||||||
|
|
||||||
|
const (
|
||||||
|
authorizeWebMessageTemplate = "authorize_web_message.tmpl"
|
||||||
|
authorizeFormPostTemplate = "authorize_form_post.tmpl"
|
||||||
|
)
|
||||||
|
|
||||||
// AuthorizeHandler is the handler for the /authorize route
|
// AuthorizeHandler is the handler for the /authorize route
|
||||||
// required params
|
// required params
|
||||||
// ?redirect_uri = redirect url
|
// ?redirect_uri = redirect url
|
||||||
|
@ -24,8 +64,6 @@ import (
|
||||||
// state[recommended] = to prevent CSRF attack (for authorizer its compulsory)
|
// state[recommended] = to prevent CSRF attack (for authorizer its compulsory)
|
||||||
// code_challenge = to prevent CSRF attack
|
// code_challenge = to prevent CSRF attack
|
||||||
// code_challenge_method = to prevent CSRF attack [only sh256 is supported]
|
// code_challenge_method = to prevent CSRF attack [only sh256 is supported]
|
||||||
|
|
||||||
// check the flow for generating and verifying codes: https://developer.okta.com/blog/2019/08/22/okta-authjs-pkce#:~:text=PKCE%20works%20by%20having%20the,is%20called%20the%20Code%20Challenge.
|
|
||||||
func AuthorizeHandler() gin.HandlerFunc {
|
func AuthorizeHandler() gin.HandlerFunc {
|
||||||
return func(gc *gin.Context) {
|
return func(gc *gin.Context) {
|
||||||
redirectURI := strings.TrimSpace(gc.Query("redirect_uri"))
|
redirectURI := strings.TrimSpace(gc.Query("redirect_uri"))
|
||||||
|
@ -34,8 +72,8 @@ func AuthorizeHandler() gin.HandlerFunc {
|
||||||
codeChallenge := strings.TrimSpace(gc.Query("code_challenge"))
|
codeChallenge := strings.TrimSpace(gc.Query("code_challenge"))
|
||||||
scopeString := strings.TrimSpace(gc.Query("scope"))
|
scopeString := strings.TrimSpace(gc.Query("scope"))
|
||||||
clientID := strings.TrimSpace(gc.Query("client_id"))
|
clientID := strings.TrimSpace(gc.Query("client_id"))
|
||||||
template := "authorize.tmpl"
|
|
||||||
responseMode := strings.TrimSpace(gc.Query("response_mode"))
|
responseMode := strings.TrimSpace(gc.Query("response_mode"))
|
||||||
|
nonce := strings.TrimSpace(gc.Query("nonce"))
|
||||||
|
|
||||||
var scope []string
|
var scope []string
|
||||||
if scopeString == "" {
|
if scopeString == "" {
|
||||||
|
@ -45,176 +83,97 @@ func AuthorizeHandler() gin.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
if responseMode == "" {
|
if responseMode == "" {
|
||||||
responseMode = "query"
|
responseMode = constants.ResponseModeQuery
|
||||||
}
|
|
||||||
|
|
||||||
if responseMode != "query" && responseMode != "web_message" {
|
|
||||||
log.Debug("Invalid response_mode: ", responseMode)
|
|
||||||
gc.JSON(400, gin.H{"error": "invalid response mode"})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if redirectURI == "" {
|
if redirectURI == "" {
|
||||||
redirectURI = "/app"
|
redirectURI = "/app"
|
||||||
}
|
}
|
||||||
|
|
||||||
isQuery := responseMode == "query"
|
|
||||||
|
|
||||||
loginURL := "/app?state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI
|
|
||||||
|
|
||||||
if clientID == "" {
|
|
||||||
if isQuery {
|
|
||||||
gc.Redirect(http.StatusFound, loginURL)
|
|
||||||
} else {
|
|
||||||
log.Debug("Failed to get client_id: ", clientID)
|
|
||||||
gc.HTML(http.StatusOK, template, gin.H{
|
|
||||||
"target_origin": redirectURI,
|
|
||||||
"authorization_response": map[string]interface{}{
|
|
||||||
"type": "authorization_response",
|
|
||||||
"response": map[string]string{
|
|
||||||
"error": "client_id is required",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if client, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID); client != clientID || err != nil {
|
|
||||||
if isQuery {
|
|
||||||
gc.Redirect(http.StatusFound, loginURL)
|
|
||||||
} else {
|
|
||||||
log.Debug("Invalid client_id: ", clientID)
|
|
||||||
gc.HTML(http.StatusOK, template, gin.H{
|
|
||||||
"target_origin": redirectURI,
|
|
||||||
"authorization_response": map[string]interface{}{
|
|
||||||
"type": "authorization_response",
|
|
||||||
"response": map[string]string{
|
|
||||||
"error": "invalid_client_id",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if state == "" {
|
|
||||||
if isQuery {
|
|
||||||
gc.Redirect(http.StatusFound, loginURL)
|
|
||||||
} else {
|
|
||||||
log.Debug("Failed to get state: ", state)
|
|
||||||
gc.HTML(http.StatusOK, template, gin.H{
|
|
||||||
"target_origin": redirectURI,
|
|
||||||
"authorization_response": map[string]interface{}{
|
|
||||||
"type": "authorization_response",
|
|
||||||
"response": map[string]string{
|
|
||||||
"error": "state is required",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if responseType == "" {
|
if responseType == "" {
|
||||||
responseType = "token"
|
responseType = "token"
|
||||||
}
|
}
|
||||||
|
|
||||||
isResponseTypeCode := responseType == "code"
|
if err := validateAuthorizeRequest(responseType, responseMode, clientID, state, codeChallenge); err != nil {
|
||||||
isResponseTypeToken := responseType == "token"
|
log.Debug("invalid authorization request: ", err)
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
if !isResponseTypeCode && !isResponseTypeToken {
|
|
||||||
if isQuery {
|
|
||||||
gc.Redirect(http.StatusFound, loginURL)
|
|
||||||
} else {
|
|
||||||
log.Debug("Invalid response_type: ", responseType)
|
|
||||||
gc.HTML(http.StatusOK, template, gin.H{
|
|
||||||
"target_origin": redirectURI,
|
|
||||||
"authorization_response": map[string]interface{}{
|
|
||||||
"type": "authorization_response",
|
|
||||||
"response": map[string]string{
|
|
||||||
"error": "response_type is invalid",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isResponseTypeCode {
|
code := uuid.New().String()
|
||||||
if codeChallenge == "" {
|
if nonce == "" {
|
||||||
if isQuery {
|
nonce = uuid.New().String()
|
||||||
gc.Redirect(http.StatusFound, loginURL)
|
|
||||||
} else {
|
|
||||||
log.Debug("Failed to get code_challenge: ", codeChallenge)
|
|
||||||
gc.HTML(http.StatusBadRequest, template, gin.H{
|
|
||||||
"target_origin": redirectURI,
|
|
||||||
"authorization_response": map[string]interface{}{
|
|
||||||
"type": "authorization_response",
|
|
||||||
"response": map[string]string{
|
|
||||||
"error": "code_challenge is required",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return
|
|
||||||
|
log := log.WithFields(log.Fields{
|
||||||
|
"response_mode": responseMode,
|
||||||
|
"response_type": responseType,
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO add state with timeout
|
||||||
|
// used for response mode query or fragment
|
||||||
|
loginState := "state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI
|
||||||
|
if responseType == constants.ResponseTypeCode {
|
||||||
|
loginState += "&code=" + code
|
||||||
|
if err := memorystore.Provider.SetState(state, code+"@@"+codeChallenge); err != nil {
|
||||||
|
log.Debug("Error setting temp code", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
loginState += "&nonce=" + nonce
|
||||||
|
if err := memorystore.Provider.SetState(state, nonce); err != nil {
|
||||||
|
log.Debug("Error setting temp code", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionToken, err := cookie.GetSession(gc)
|
loginURL := "/app?" + loginState
|
||||||
if err != nil {
|
|
||||||
if isQuery {
|
if responseMode == constants.ResponseModeFragment {
|
||||||
gc.Redirect(http.StatusFound, loginURL)
|
loginURL = "/app#" + loginState
|
||||||
} else {
|
}
|
||||||
gc.HTML(http.StatusOK, template, gin.H{
|
|
||||||
"target_origin": redirectURI,
|
if responseType == constants.ResponseTypeCode && codeChallenge == "" {
|
||||||
"authorization_response": map[string]interface{}{
|
handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{
|
||||||
"type": "authorization_response",
|
"type": "authorization_response",
|
||||||
"response": map[string]string{
|
"response": map[string]interface{}{
|
||||||
|
"error": "code_challenge_required",
|
||||||
|
"error_description": "code challenge is required",
|
||||||
|
},
|
||||||
|
}, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
loginError := map[string]interface{}{
|
||||||
|
"type": "authorization_response",
|
||||||
|
"response": map[string]interface{}{
|
||||||
"error": "login_required",
|
"error": "login_required",
|
||||||
"error_description": "Login is required",
|
"error_description": "Login is required",
|
||||||
},
|
},
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
sessionToken, err := cookie.GetSession(gc)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("GetSession failed: ", err)
|
||||||
|
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// get session from cookie
|
// get session from cookie
|
||||||
claims, err := token.ValidateBrowserSession(gc, sessionToken)
|
claims, err := token.ValidateBrowserSession(gc, sessionToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isQuery {
|
log.Debug("ValidateBrowserSession failed: ", err)
|
||||||
gc.Redirect(http.StatusFound, loginURL)
|
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
|
||||||
} else {
|
|
||||||
gc.HTML(http.StatusOK, template, gin.H{
|
|
||||||
"target_origin": redirectURI,
|
|
||||||
"authorization_response": map[string]interface{}{
|
|
||||||
"type": "authorization_response",
|
|
||||||
"response": map[string]string{
|
|
||||||
"error": "login_required",
|
|
||||||
"error_description": "Login is required",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userID := claims.Subject
|
userID := claims.Subject
|
||||||
user, err := db.Provider.GetUserByID(gc, userID)
|
user, err := db.Provider.GetUserByID(gc, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isQuery {
|
log.Debug("GetUserByID failed: ", err)
|
||||||
gc.Redirect(http.StatusFound, loginURL)
|
handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{
|
||||||
} else {
|
|
||||||
gc.HTML(http.StatusOK, template, gin.H{
|
|
||||||
"target_origin": redirectURI,
|
|
||||||
"authorization_response": map[string]interface{}{
|
|
||||||
"type": "authorization_response",
|
"type": "authorization_response",
|
||||||
"response": map[string]string{
|
"response": map[string]interface{}{
|
||||||
"error": "signup_required",
|
"error": "signup_required",
|
||||||
"error_description": "Sign up required",
|
"error_description": "Sign up required",
|
||||||
},
|
},
|
||||||
},
|
}, http.StatusOK)
|
||||||
})
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,81 +182,102 @@ func AuthorizeHandler() gin.HandlerFunc {
|
||||||
sessionKey = claims.LoginMethod + ":" + user.ID
|
sessionKey = claims.LoginMethod + ":" + user.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
// if user is logged in
|
|
||||||
// based on the response type code, generate the response
|
|
||||||
if isResponseTypeCode {
|
|
||||||
// rollover the session for security
|
// rollover the session for security
|
||||||
go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce)
|
go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce)
|
||||||
nonce := uuid.New().String()
|
if responseType == constants.ResponseTypeCode {
|
||||||
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope, claims.LoginMethod)
|
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope, claims.LoginMethod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isQuery {
|
log.Debug("CreateSessionToken failed: ", err)
|
||||||
gc.Redirect(http.StatusFound, loginURL)
|
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
|
||||||
} else {
|
return
|
||||||
gc.HTML(http.StatusOK, template, gin.H{
|
}
|
||||||
"target_origin": redirectURI,
|
|
||||||
"authorization_response": map[string]interface{}{
|
// TODO: add state with timeout
|
||||||
"type": "authorization_response",
|
// if err := memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken); err != nil {
|
||||||
"response": map[string]string{
|
// log.Debug("SetState failed: ", err)
|
||||||
"error": "login_required",
|
// handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
|
||||||
"error_description": "Login is required",
|
// return
|
||||||
},
|
// }
|
||||||
},
|
|
||||||
})
|
// TODO: add state with timeout
|
||||||
}
|
if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+newSessionToken); err != nil {
|
||||||
|
log.Debug("SetState failed: ", err)
|
||||||
|
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken); err != nil {
|
||||||
|
log.Debug("SetUserSession failed: ", err)
|
||||||
|
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+newSessionTokenData.Nonce, newSessionToken)
|
|
||||||
cookie.SetSession(gc, newSessionToken)
|
cookie.SetSession(gc, newSessionToken)
|
||||||
code := uuid.New().String()
|
|
||||||
memorystore.Provider.SetState(codeChallenge, code+"@"+newSessionToken)
|
// in case, response type is code and user is already logged in send the code and state
|
||||||
gc.HTML(http.StatusOK, template, gin.H{
|
// and cookie session will already be rolled over and set
|
||||||
"target_origin": redirectURI,
|
// gc.HTML(http.StatusOK, authorizeWebMessageTemplate, gin.H{
|
||||||
"authorization_response": map[string]interface{}{
|
// "target_origin": redirectURI,
|
||||||
|
// "authorization_response": map[string]interface{}{
|
||||||
|
// "type": "authorization_response",
|
||||||
|
// "response": map[string]string{
|
||||||
|
// "code": code,
|
||||||
|
// "state": state,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// })
|
||||||
|
|
||||||
|
params := "code=" + code + "&state=" + state + "&nonce=" + nonce
|
||||||
|
if responseMode == constants.ResponseModeQuery {
|
||||||
|
if strings.Contains(redirectURI, "?") {
|
||||||
|
redirectURI = redirectURI + "&" + params
|
||||||
|
} else {
|
||||||
|
redirectURI = redirectURI + "?" + params
|
||||||
|
}
|
||||||
|
} else if responseMode == constants.ResponseModeFragment {
|
||||||
|
if strings.Contains(redirectURI, "#") {
|
||||||
|
redirectURI = redirectURI + "&" + params
|
||||||
|
} else {
|
||||||
|
redirectURI = redirectURI + "#" + params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{
|
||||||
"type": "authorization_response",
|
"type": "authorization_response",
|
||||||
"response": map[string]string{
|
"response": map[string]interface{}{
|
||||||
"code": code,
|
"code": code,
|
||||||
"state": state,
|
"state": state,
|
||||||
},
|
},
|
||||||
},
|
}, http.StatusOK)
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isResponseTypeToken {
|
if responseType == constants.ResponseTypeToken || responseType == constants.ResponseTypeIDToken {
|
||||||
// rollover the session for security
|
// rollover the session for security
|
||||||
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod)
|
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope, claims.LoginMethod, nonce, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isQuery {
|
log.Debug("CreateAuthToken failed: ", err)
|
||||||
gc.Redirect(http.StatusFound, loginURL)
|
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
|
||||||
} else {
|
return
|
||||||
gc.HTML(http.StatusOK, template, gin.H{
|
}
|
||||||
"target_origin": redirectURI,
|
|
||||||
"authorization_response": map[string]interface{}{
|
if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+nonce, authToken.FingerPrintHash); err != nil {
|
||||||
"type": "authorization_response",
|
log.Debug("SetUserSession failed: ", err)
|
||||||
"response": map[string]string{
|
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
|
||||||
"error": "login_required",
|
return
|
||||||
"error_description": "Login is required",
|
}
|
||||||
},
|
|
||||||
},
|
if err := memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+nonce, authToken.FingerPrintHash); err != nil {
|
||||||
})
|
log.Debug("SetUserSession failed: ", err)
|
||||||
}
|
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce)
|
|
||||||
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
|
|
||||||
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
|
|
||||||
cookie.SetSession(gc, authToken.FingerPrintHash)
|
cookie.SetSession(gc, authToken.FingerPrintHash)
|
||||||
|
|
||||||
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
|
||||||
if expiresIn <= 0 {
|
|
||||||
expiresIn = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// used of query mode
|
// used of query mode
|
||||||
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token
|
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(authToken.IDToken.ExpiresAt, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token
|
||||||
|
|
||||||
res := map[string]interface{}{
|
res := map[string]interface{}{
|
||||||
"access_token": authToken.AccessToken.Token,
|
"access_token": authToken.AccessToken.Token,
|
||||||
|
@ -305,7 +285,12 @@ func AuthorizeHandler() gin.HandlerFunc {
|
||||||
"state": state,
|
"state": state,
|
||||||
"scope": scope,
|
"scope": scope,
|
||||||
"token_type": "Bearer",
|
"token_type": "Bearer",
|
||||||
"expires_in": expiresIn,
|
"expires_in": authToken.AccessToken.ExpiresAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
if nonce != "" {
|
||||||
|
params += "&nonce=" + nonce
|
||||||
|
res["nonce"] = nonce
|
||||||
}
|
}
|
||||||
|
|
||||||
if authToken.RefreshToken != nil {
|
if authToken.RefreshToken != nil {
|
||||||
|
@ -314,38 +299,77 @@ func AuthorizeHandler() gin.HandlerFunc {
|
||||||
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
|
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
|
||||||
}
|
}
|
||||||
|
|
||||||
if isQuery {
|
if responseMode == constants.ResponseModeQuery {
|
||||||
if strings.Contains(redirectURI, "?") {
|
if strings.Contains(redirectURI, "?") {
|
||||||
gc.Redirect(http.StatusFound, redirectURI+"&"+params)
|
redirectURI = redirectURI + "&" + params
|
||||||
} else {
|
} else {
|
||||||
gc.Redirect(http.StatusFound, redirectURI+"?"+params)
|
redirectURI = redirectURI + "?" + params
|
||||||
}
|
}
|
||||||
|
} else if responseMode == constants.ResponseModeFragment {
|
||||||
|
if strings.Contains(redirectURI, "#") {
|
||||||
|
redirectURI = redirectURI + "&" + params
|
||||||
} else {
|
} else {
|
||||||
gc.HTML(http.StatusOK, template, gin.H{
|
redirectURI = redirectURI + "#" + params
|
||||||
"target_origin": redirectURI,
|
}
|
||||||
"authorization_response": map[string]interface{}{
|
}
|
||||||
|
|
||||||
|
handleResponse(gc, responseMode, loginURL, redirectURI, map[string]interface{}{
|
||||||
"type": "authorization_response",
|
"type": "authorization_response",
|
||||||
"response": res,
|
"response": res,
|
||||||
},
|
}, http.StatusOK)
|
||||||
})
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if isQuery {
|
handleResponse(gc, responseMode, loginURL, redirectURI, loginError, http.StatusOK)
|
||||||
gc.Redirect(http.StatusFound, loginURL)
|
}
|
||||||
} else {
|
}
|
||||||
// by default return with error
|
|
||||||
gc.HTML(http.StatusOK, template, gin.H{
|
func validateAuthorizeRequest(responseType, responseMode, clientID, state, codeChallenge string) error {
|
||||||
|
if strings.TrimSpace(state) == "" {
|
||||||
|
return fmt.Errorf("invalid state. state is required to prevent csrf attack", responseMode)
|
||||||
|
}
|
||||||
|
if responseType != constants.ResponseTypeCode && responseType != constants.ResponseTypeToken && responseType != constants.ResponseTypeIDToken {
|
||||||
|
return fmt.Errorf("invalid response type %s. 'code' & 'token' are valid response_type", responseMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if responseMode != constants.ResponseModeQuery && responseMode != constants.ResponseModeWebMessage && responseMode != constants.ResponseModeFragment && responseMode != constants.ResponseModeFormPost {
|
||||||
|
return fmt.Errorf("invalid response mode %s. 'query', 'fragment', 'form_post' and 'web_message' are valid response_mode", responseMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if client, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientID); client != clientID || err != nil {
|
||||||
|
return fmt.Errorf("invalid client_id %s", clientID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleResponse(gc *gin.Context, responseMode, loginURI, redirectURI string, data map[string]interface{}, httpStatusCode int) {
|
||||||
|
isAuthenticationRequired := false
|
||||||
|
if _, ok := data["response"].(map[string]interface{})["error"]; ok {
|
||||||
|
isAuthenticationRequired = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if isAuthenticationRequired {
|
||||||
|
gc.Redirect(http.StatusFound, loginURI)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch responseMode {
|
||||||
|
case constants.ResponseModeQuery, constants.ResponseModeFragment:
|
||||||
|
|
||||||
|
gc.Redirect(http.StatusFound, redirectURI)
|
||||||
|
return
|
||||||
|
case constants.ResponseModeWebMessage:
|
||||||
|
gc.HTML(httpStatusCode, authorizeWebMessageTemplate, gin.H{
|
||||||
"target_origin": redirectURI,
|
"target_origin": redirectURI,
|
||||||
"authorization_response": map[string]interface{}{
|
"authorization_response": data,
|
||||||
"type": "authorization_response",
|
|
||||||
"response": map[string]string{
|
|
||||||
"error": "login_required",
|
|
||||||
"error_description": "Login is required",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
return
|
||||||
|
case constants.ResponseModeFormPost:
|
||||||
|
gc.HTML(httpStatusCode, authorizeFormPostTemplate, gin.H{
|
||||||
|
"target_origin": redirectURI,
|
||||||
|
"authorization_response": data["response"],
|
||||||
|
})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
|
|
||||||
"github.com/coreos/go-oidc/v3/oidc"
|
"github.com/coreos/go-oidc/v3/oidc"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
|
@ -55,20 +56,20 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
||||||
scopes := strings.Split(sessionSplit[3], ",")
|
scopes := strings.Split(sessionSplit[3], ",")
|
||||||
|
|
||||||
user := models.User{}
|
user := models.User{}
|
||||||
code := ctx.Request.FormValue("code")
|
oauthCode := ctx.Request.FormValue("code")
|
||||||
switch provider {
|
switch provider {
|
||||||
case constants.AuthRecipeMethodGoogle:
|
case constants.AuthRecipeMethodGoogle:
|
||||||
user, err = processGoogleUserInfo(code)
|
user, err = processGoogleUserInfo(oauthCode)
|
||||||
case constants.AuthRecipeMethodGithub:
|
case constants.AuthRecipeMethodGithub:
|
||||||
user, err = processGithubUserInfo(code)
|
user, err = processGithubUserInfo(oauthCode)
|
||||||
case constants.AuthRecipeMethodFacebook:
|
case constants.AuthRecipeMethodFacebook:
|
||||||
user, err = processFacebookUserInfo(code)
|
user, err = processFacebookUserInfo(oauthCode)
|
||||||
case constants.AuthRecipeMethodLinkedIn:
|
case constants.AuthRecipeMethodLinkedIn:
|
||||||
user, err = processLinkedInUserInfo(code)
|
user, err = processLinkedInUserInfo(oauthCode)
|
||||||
case constants.AuthRecipeMethodApple:
|
case constants.AuthRecipeMethodApple:
|
||||||
user, err = processAppleUserInfo(code)
|
user, err = processAppleUserInfo(oauthCode)
|
||||||
case constants.AuthRecipeMethodTwitter:
|
case constants.AuthRecipeMethodTwitter:
|
||||||
user, err = processTwitterUserInfo(code, sessionState)
|
user, err = processTwitterUserInfo(oauthCode, sessionState)
|
||||||
default:
|
default:
|
||||||
log.Info("Invalid oauth provider")
|
log.Info("Invalid oauth provider")
|
||||||
err = fmt.Errorf(`invalid oauth provider`)
|
err = fmt.Errorf(`invalid oauth provider`)
|
||||||
|
@ -196,18 +197,53 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
authToken, err := token.CreateAuthToken(ctx, user, inputRoles, scopes, provider)
|
// TODO
|
||||||
|
// use stateValue to get code / nonce
|
||||||
|
// add code / nonce to id_token
|
||||||
|
code := ""
|
||||||
|
codeChallenge := ""
|
||||||
|
nonce := ""
|
||||||
|
if stateValue != "" {
|
||||||
|
// Get state from store
|
||||||
|
authorizeState, _ := memorystore.Provider.GetState(stateValue)
|
||||||
|
if authorizeState != "" {
|
||||||
|
authorizeStateSplit := strings.Split(authorizeState, "@@")
|
||||||
|
if len(authorizeStateSplit) > 1 {
|
||||||
|
code = authorizeStateSplit[0]
|
||||||
|
codeChallenge = authorizeStateSplit[1]
|
||||||
|
} else {
|
||||||
|
nonce = authorizeState
|
||||||
|
}
|
||||||
|
go memorystore.Provider.RemoveState(stateValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nonce == "" {
|
||||||
|
nonce = uuid.New().String()
|
||||||
|
}
|
||||||
|
authToken, err := token.CreateAuthToken(ctx, user, inputRoles, scopes, provider, nonce, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Failed to create auth token: ", err)
|
log.Debug("Failed to create auth token: ", err)
|
||||||
ctx.JSON(500, gin.H{"error": err.Error()})
|
ctx.JSON(500, gin.H{"error": err.Error()})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Code challenge could be optional if PKCE flow is not used
|
||||||
|
if code != "" {
|
||||||
|
if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil {
|
||||||
|
log.Debug("SetState failed: ", err)
|
||||||
|
ctx.JSON(500, gin.H{"error": err.Error()})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
if expiresIn <= 0 {
|
if expiresIn <= 0 {
|
||||||
expiresIn = 1
|
expiresIn = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token
|
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token + "&nonce=" + nonce
|
||||||
|
|
||||||
|
if code != "" {
|
||||||
|
params += "&code=" + code
|
||||||
|
}
|
||||||
|
|
||||||
sessionKey := provider + ":" + user.ID
|
sessionKey := provider + ":" + user.ID
|
||||||
cookie.SetSession(ctx, authToken.FingerPrintHash)
|
cookie.SetSession(ctx, authToken.FingerPrintHash)
|
||||||
|
@ -215,7 +251,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
||||||
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
|
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
|
||||||
|
|
||||||
if authToken.RefreshToken != nil {
|
if authToken.RefreshToken != nil {
|
||||||
params = params + `&refresh_token=` + authToken.RefreshToken.Token
|
params += `&refresh_token=` + authToken.RefreshToken.Token
|
||||||
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
|
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeRefreshToken+"_"+authToken.FingerPrint, authToken.RefreshToken.Token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
@ -17,12 +18,22 @@ import (
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type RequestBody struct {
|
||||||
|
CodeVerifier string `form:"code_verifier" json:"code_verifier"`
|
||||||
|
Code string `form:"code" json:"code"`
|
||||||
|
ClientID string `form:"client_id" json:"client_id"`
|
||||||
|
ClientSecret string `form:"client_secret" json:"client_secret"`
|
||||||
|
GrantType string `form:"grant_type" json:"grant_type"`
|
||||||
|
RefreshToken string `form:"refresh_token" json:"refresh_token"`
|
||||||
|
RedirectURI string `form:"redirect_uri" json:"redirect_uri"`
|
||||||
|
}
|
||||||
|
|
||||||
// TokenHandler to handle /oauth/token requests
|
// TokenHandler to handle /oauth/token requests
|
||||||
// grant type required
|
// grant type required
|
||||||
func TokenHandler() gin.HandlerFunc {
|
func TokenHandler() gin.HandlerFunc {
|
||||||
return func(gc *gin.Context) {
|
return func(gc *gin.Context) {
|
||||||
var reqBody map[string]string
|
var reqBody RequestBody
|
||||||
if err := gc.BindJSON(&reqBody); err != nil {
|
if err := gc.Bind(&reqBody); err != nil {
|
||||||
log.Debug("Error binding JSON: ", err)
|
log.Debug("Error binding JSON: ", err)
|
||||||
gc.JSON(http.StatusBadRequest, gin.H{
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "error_binding_json",
|
"error": "error_binding_json",
|
||||||
|
@ -31,11 +42,12 @@ func TokenHandler() gin.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
codeVerifier := strings.TrimSpace(reqBody["code_verifier"])
|
codeVerifier := strings.TrimSpace(reqBody.CodeVerifier)
|
||||||
code := strings.TrimSpace(reqBody["code"])
|
code := strings.TrimSpace(reqBody.Code)
|
||||||
clientID := strings.TrimSpace(reqBody["client_id"])
|
clientID := strings.TrimSpace(reqBody.ClientID)
|
||||||
grantType := strings.TrimSpace(reqBody["grant_type"])
|
grantType := strings.TrimSpace(reqBody.GrantType)
|
||||||
refreshToken := strings.TrimSpace(reqBody["refresh_token"])
|
refreshToken := strings.TrimSpace(reqBody.RefreshToken)
|
||||||
|
clientSecret := strings.TrimSpace(reqBody.ClientSecret)
|
||||||
|
|
||||||
if grantType == "" {
|
if grantType == "" {
|
||||||
grantType = "authorization_code"
|
grantType = "authorization_code"
|
||||||
|
@ -76,15 +88,6 @@ func TokenHandler() gin.HandlerFunc {
|
||||||
sessionKey := ""
|
sessionKey := ""
|
||||||
|
|
||||||
if isAuthorizationCodeGrant {
|
if isAuthorizationCodeGrant {
|
||||||
if codeVerifier == "" {
|
|
||||||
log.Debug("Code verifier is empty")
|
|
||||||
gc.JSON(http.StatusBadRequest, gin.H{
|
|
||||||
"error": "invalid_code_verifier",
|
|
||||||
"error_description": "The code verifier is required",
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if code == "" {
|
if code == "" {
|
||||||
log.Debug("Code is empty")
|
log.Debug("Code is empty")
|
||||||
gc.JSON(http.StatusBadRequest, gin.H{
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
@ -94,14 +97,37 @@ func TokenHandler() gin.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hash := sha256.New()
|
if codeVerifier == "" && clientSecret == "" {
|
||||||
hash.Write([]byte(codeVerifier))
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
encryptedCode := strings.ReplaceAll(base64.URLEncoding.EncodeToString(hash.Sum(nil)), "+", "-")
|
"error": "invalid_dat",
|
||||||
encryptedCode = strings.ReplaceAll(encryptedCode, "/", "_")
|
"error_description": "The code verifier or client secret is required",
|
||||||
encryptedCode = strings.ReplaceAll(encryptedCode, "=", "")
|
})
|
||||||
sessionData, err := memorystore.Provider.GetState(encryptedCode)
|
return
|
||||||
|
}
|
||||||
|
// Get state
|
||||||
|
sessionData, err := memorystore.Provider.GetState(code)
|
||||||
if sessionData == "" || err != nil {
|
if sessionData == "" || err != nil {
|
||||||
log.Debug("Session data is empty")
|
log.Debug("Session data is empty")
|
||||||
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
|
"error": "invalid_code",
|
||||||
|
"error_description": "The code is invalid",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// [0] -> code_challenge
|
||||||
|
// [1] -> session cookie
|
||||||
|
sessionDataSplit := strings.Split(sessionData, "@@")
|
||||||
|
|
||||||
|
go memorystore.Provider.RemoveState(code)
|
||||||
|
|
||||||
|
if codeVerifier != "" {
|
||||||
|
hash := sha256.New()
|
||||||
|
hash.Write([]byte(codeVerifier))
|
||||||
|
encryptedCode := strings.ReplaceAll(base64.RawURLEncoding.EncodeToString(hash.Sum(nil)), "+", "-")
|
||||||
|
encryptedCode = strings.ReplaceAll(encryptedCode, "/", "_")
|
||||||
|
encryptedCode = strings.ReplaceAll(encryptedCode, "=", "")
|
||||||
|
if encryptedCode != sessionDataSplit[0] {
|
||||||
gc.JSON(http.StatusBadRequest, gin.H{
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "invalid_code_verifier",
|
"error": "invalid_code_verifier",
|
||||||
"error_description": "The code verifier is invalid",
|
"error_description": "The code verifier is invalid",
|
||||||
|
@ -109,19 +135,16 @@ func TokenHandler() gin.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
go memorystore.Provider.RemoveState(encryptedCode)
|
} else {
|
||||||
// split session data
|
if clientHash, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyClientSecret); clientSecret != clientHash || err != nil {
|
||||||
// it contains code@sessiontoken
|
log.Debug("Client Secret is invalid: ", clientID)
|
||||||
sessionDataSplit := strings.Split(sessionData, "@")
|
|
||||||
|
|
||||||
if sessionDataSplit[0] != code {
|
|
||||||
log.Debug("Invalid code verifier. Unable to split session data")
|
|
||||||
gc.JSON(http.StatusBadRequest, gin.H{
|
gc.JSON(http.StatusBadRequest, gin.H{
|
||||||
"error": "invalid_code_verifier",
|
"error": "invalid_client_secret",
|
||||||
"error_description": "The code verifier is invalid",
|
"error_description": "The client secret is invalid",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// validate session
|
// validate session
|
||||||
claims, err := token.ValidateBrowserSession(gc, sessionDataSplit[1])
|
claims, err := token.ValidateBrowserSession(gc, sessionDataSplit[1])
|
||||||
|
@ -146,6 +169,7 @@ func TokenHandler() gin.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce)
|
go memorystore.Provider.DeleteUserSession(sessionKey, claims.Nonce)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// validate refresh token
|
// validate refresh token
|
||||||
if refreshToken == "" {
|
if refreshToken == "" {
|
||||||
|
@ -206,7 +230,8 @@ func TokenHandler() gin.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod)
|
nonce := uuid.New().String() + "@@" + code
|
||||||
|
authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Error creating auth token: ", err)
|
log.Debug("Error creating auth token: ", err)
|
||||||
gc.JSON(http.StatusUnauthorized, gin.H{
|
gc.JSON(http.StatusUnauthorized, gin.H{
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
@ -98,7 +99,30 @@ func VerifyEmailHandler() gin.HandlerFunc {
|
||||||
if verificationRequest.Identifier == constants.VerificationTypeMagicLinkLogin {
|
if verificationRequest.Identifier == constants.VerificationTypeMagicLinkLogin {
|
||||||
loginMethod = constants.AuthRecipeMethodMagicLinkLogin
|
loginMethod = constants.AuthRecipeMethodMagicLinkLogin
|
||||||
}
|
}
|
||||||
authToken, err := token.CreateAuthToken(c, user, roles, scope, loginMethod)
|
|
||||||
|
code := ""
|
||||||
|
// Not required as /oauth/token cannot be resumed from other tab
|
||||||
|
// codeChallenge := ""
|
||||||
|
nonce := ""
|
||||||
|
if state != "" {
|
||||||
|
// Get state from store
|
||||||
|
authorizeState, _ := memorystore.Provider.GetState(state)
|
||||||
|
if authorizeState != "" {
|
||||||
|
authorizeStateSplit := strings.Split(authorizeState, "@@")
|
||||||
|
if len(authorizeStateSplit) > 1 {
|
||||||
|
code = authorizeStateSplit[0]
|
||||||
|
// Not required as /oauth/token cannot be resumed from other tab
|
||||||
|
// codeChallenge = authorizeStateSplit[1]
|
||||||
|
} else {
|
||||||
|
nonce = authorizeState
|
||||||
|
}
|
||||||
|
go memorystore.Provider.RemoveState(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nonce == "" {
|
||||||
|
nonce = uuid.New().String()
|
||||||
|
}
|
||||||
|
authToken, err := token.CreateAuthToken(c, user, roles, scope, loginMethod, nonce, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Error creating auth token: ", err)
|
log.Debug("Error creating auth token: ", err)
|
||||||
errorRes["error_description"] = err.Error()
|
errorRes["error_description"] = err.Error()
|
||||||
|
@ -106,12 +130,27 @@ func VerifyEmailHandler() gin.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Code challenge could be optional if PKCE flow is not used
|
||||||
|
// Not required as /oauth/token cannot be resumed from other tab
|
||||||
|
// if code != "" {
|
||||||
|
// if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil {
|
||||||
|
// log.Debug("Error setting code state ", err)
|
||||||
|
// errorRes["error_description"] = err.Error()
|
||||||
|
// c.JSON(500, errorRes)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
if expiresIn <= 0 {
|
if expiresIn <= 0 {
|
||||||
expiresIn = 1
|
expiresIn = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token
|
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token + "&nonce=" + nonce
|
||||||
|
|
||||||
|
if code != "" {
|
||||||
|
params += "&code=" + code
|
||||||
|
}
|
||||||
|
|
||||||
sessionKey := loginMethod + ":" + user.ID
|
sessionKey := loginMethod + ":" + user.ID
|
||||||
cookie.SetSession(c, authToken.FingerPrintHash)
|
cookie.SetSession(c, authToken.FingerPrintHash)
|
||||||
|
|
|
@ -91,7 +91,7 @@ func GetDomainName(uri string) string {
|
||||||
return host
|
return host
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAppURL to get /app/ url if not configured by user
|
// GetAppURL to get /app url if not configured by user
|
||||||
func GetAppURL(gc *gin.Context) string {
|
func GetAppURL(gc *gin.Context) string {
|
||||||
envAppURL, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAppURL)
|
envAppURL, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAppURL)
|
||||||
if envAppURL == "" || err != nil {
|
if envAppURL == "" || err != nil {
|
||||||
|
|
|
@ -94,7 +94,7 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// exec it as go routine so that we can reduce the api latency
|
// execute it as go routine so that we can reduce the api latency
|
||||||
go email.SendEmail([]string{params.Email}, constants.VerificationTypeForgotPassword, map[string]interface{}{
|
go email.SendEmail([]string{params.Email}, constants.VerificationTypeForgotPassword, map[string]interface{}{
|
||||||
"user": user.ToMap(),
|
"user": user.ToMap(),
|
||||||
"organization": utils.GetOrganization(),
|
"organization": utils.GetOrganization(),
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
|
@ -140,12 +141,43 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth)
|
code := ""
|
||||||
|
codeChallenge := ""
|
||||||
|
nonce := ""
|
||||||
|
if params.State != nil {
|
||||||
|
// Get state from store
|
||||||
|
authorizeState, _ := memorystore.Provider.GetState(refs.StringValue(params.State))
|
||||||
|
if authorizeState != "" {
|
||||||
|
authorizeStateSplit := strings.Split(authorizeState, "@@")
|
||||||
|
if len(authorizeStateSplit) > 1 {
|
||||||
|
code = authorizeStateSplit[0]
|
||||||
|
codeChallenge = authorizeStateSplit[1]
|
||||||
|
} else {
|
||||||
|
nonce = authorizeState
|
||||||
|
}
|
||||||
|
go memorystore.Provider.RemoveState(refs.StringValue(params.State))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nonce == "" {
|
||||||
|
nonce = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Failed to create auth token", err)
|
log.Debug("Failed to create auth token", err)
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO add to other login options as well
|
||||||
|
// Code challenge could be optional if PKCE flow is not used
|
||||||
|
if code != "" {
|
||||||
|
if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil {
|
||||||
|
log.Debug("SetState failed: ", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
if expiresIn <= 0 {
|
if expiresIn <= 0 {
|
||||||
expiresIn = 1
|
expiresIn = 1
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
"github.com/authorizerdev/authorizer/server/parsers"
|
"github.com/authorizerdev/authorizer/server/parsers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/authorizerdev/authorizer/server/validators"
|
"github.com/authorizerdev/authorizer/server/validators"
|
||||||
|
@ -185,7 +186,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
|
||||||
}
|
}
|
||||||
redirectURLParams := "&roles=" + strings.Join(inputRoles, ",")
|
redirectURLParams := "&roles=" + strings.Join(inputRoles, ",")
|
||||||
if params.State != nil {
|
if params.State != nil {
|
||||||
redirectURLParams = redirectURLParams + "&state=" + *params.State
|
redirectURLParams = redirectURLParams + "&state=" + refs.StringValue(params.State)
|
||||||
}
|
}
|
||||||
if params.Scope != nil && len(params.Scope) > 0 {
|
if params.Scope != nil && len(params.Scope) > 0 {
|
||||||
redirectURLParams = redirectURLParams + "&scope=" + strings.Join(params.Scope, " ")
|
redirectURLParams = redirectURLParams + "&scope=" + strings.Join(params.Scope, " ")
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
@ -70,7 +71,8 @@ func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*mod
|
||||||
scope = params.Scope
|
scope = params.Scope
|
||||||
}
|
}
|
||||||
|
|
||||||
authToken, err := token.CreateAuthToken(gc, user, claimRoles, scope, claims.LoginMethod)
|
nonce := uuid.New().String()
|
||||||
|
authToken, err := token.CreateAuthToken(gc, user, claimRoles, scope, claims.LoginMethod, nonce, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Failed to create auth token: ", err)
|
log.Debug("Failed to create auth token: ", err)
|
||||||
return res, err
|
return res, err
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
@ -242,12 +243,42 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
|
||||||
scope = params.Scope
|
scope = params.Scope
|
||||||
}
|
}
|
||||||
|
|
||||||
authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth)
|
code := ""
|
||||||
|
codeChallenge := ""
|
||||||
|
nonce := ""
|
||||||
|
if params.State != nil {
|
||||||
|
// Get state from store
|
||||||
|
authorizeState, _ := memorystore.Provider.GetState(refs.StringValue(params.State))
|
||||||
|
if authorizeState != "" {
|
||||||
|
authorizeStateSplit := strings.Split(authorizeState, "@@")
|
||||||
|
if len(authorizeStateSplit) > 1 {
|
||||||
|
code = authorizeStateSplit[0]
|
||||||
|
codeChallenge = authorizeStateSplit[1]
|
||||||
|
} else {
|
||||||
|
nonce = authorizeState
|
||||||
|
}
|
||||||
|
go memorystore.Provider.RemoveState(refs.StringValue(params.State))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nonce == "" {
|
||||||
|
nonce = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Failed to create auth token: ", err)
|
log.Debug("Failed to create auth token: ", err)
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Code challenge could be optional if PKCE flow is not used
|
||||||
|
if code != "" {
|
||||||
|
if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil {
|
||||||
|
log.Debug("SetState failed: ", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
expiresIn := authToken.AccessToken.ExpiresAt - time.Now().Unix()
|
||||||
if expiresIn <= 0 {
|
if expiresIn <= 0 {
|
||||||
expiresIn = 1
|
expiresIn = 1
|
||||||
|
|
|
@ -125,7 +125,6 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
|
||||||
return res, fmt.Errorf("user with this email address already exists")
|
return res, fmt.Errorf("user with this email address already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO figure out how to do this
|
|
||||||
go memorystore.Provider.DeleteAllUserSessions(user.ID)
|
go memorystore.Provider.DeleteAllUserSessions(user.ID)
|
||||||
|
|
||||||
hostname := parsers.GetHost(gc)
|
hostname := parsers.GetHost(gc)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
@ -15,6 +16,7 @@ import (
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
"github.com/authorizerdev/authorizer/server/parsers"
|
"github.com/authorizerdev/authorizer/server/parsers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
)
|
)
|
||||||
|
@ -84,12 +86,42 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
|
||||||
|
|
||||||
roles := strings.Split(user.Roles, ",")
|
roles := strings.Split(user.Roles, ",")
|
||||||
scope := []string{"openid", "email", "profile"}
|
scope := []string{"openid", "email", "profile"}
|
||||||
authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod)
|
code := ""
|
||||||
|
// Not required as /oauth/token cannot be resumed from other tab
|
||||||
|
// codeChallenge := ""
|
||||||
|
nonce := ""
|
||||||
|
if params.State != nil {
|
||||||
|
// Get state from store
|
||||||
|
authorizeState, _ := memorystore.Provider.GetState(refs.StringValue(params.State))
|
||||||
|
if authorizeState != "" {
|
||||||
|
authorizeStateSplit := strings.Split(authorizeState, "@@")
|
||||||
|
if len(authorizeStateSplit) > 1 {
|
||||||
|
code = authorizeStateSplit[0]
|
||||||
|
// Not required as /oauth/token cannot be resumed from other tab
|
||||||
|
// codeChallenge = authorizeStateSplit[1]
|
||||||
|
} else {
|
||||||
|
nonce = authorizeState
|
||||||
|
}
|
||||||
|
go memorystore.Provider.RemoveState(refs.StringValue(params.State))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nonce == "" {
|
||||||
|
nonce = uuid.New().String()
|
||||||
|
}
|
||||||
|
authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Failed to create auth token: ", err)
|
log.Debug("Failed to create auth token: ", err)
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Code challenge could be optional if PKCE flow is not used
|
||||||
|
// Not required as /oauth/token cannot be resumed from other tab
|
||||||
|
// if code != "" {
|
||||||
|
// if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil {
|
||||||
|
// log.Debug("SetState failed: ", err)
|
||||||
|
// return res, err
|
||||||
|
// }
|
||||||
|
// }
|
||||||
go func() {
|
go func() {
|
||||||
if isSignUp {
|
if isSignUp {
|
||||||
utils.RegisterEvent(ctx, constants.UserSignUpWebhookEvent, loginMethod, user)
|
utils.RegisterEvent(ctx, constants.UserSignUpWebhookEvent, loginMethod, user)
|
||||||
|
|
|
@ -12,8 +12,10 @@ import (
|
||||||
"github.com/authorizerdev/authorizer/server/db/models"
|
"github.com/authorizerdev/authorizer/server/db/models"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/memorystore"
|
"github.com/authorizerdev/authorizer/server/memorystore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/refs"
|
||||||
"github.com/authorizerdev/authorizer/server/token"
|
"github.com/authorizerdev/authorizer/server/token"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
"github.com/google/uuid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -57,12 +59,40 @@ func VerifyOtpResolver(ctx context.Context, params model.VerifyOTPRequest) (*mod
|
||||||
|
|
||||||
roles := strings.Split(user.Roles, ",")
|
roles := strings.Split(user.Roles, ",")
|
||||||
scope := []string{"openid", "email", "profile"}
|
scope := []string{"openid", "email", "profile"}
|
||||||
authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod)
|
code := ""
|
||||||
|
codeChallenge := ""
|
||||||
|
nonce := ""
|
||||||
|
if params.State != nil {
|
||||||
|
// Get state from store
|
||||||
|
authorizeState, _ := memorystore.Provider.GetState(refs.StringValue(params.State))
|
||||||
|
if authorizeState != "" {
|
||||||
|
authorizeStateSplit := strings.Split(authorizeState, "@@")
|
||||||
|
if len(authorizeStateSplit) > 1 {
|
||||||
|
code = authorizeStateSplit[0]
|
||||||
|
codeChallenge = authorizeStateSplit[1]
|
||||||
|
} else {
|
||||||
|
nonce = authorizeState
|
||||||
|
}
|
||||||
|
go memorystore.Provider.RemoveState(refs.StringValue(params.State))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if nonce == "" {
|
||||||
|
nonce = uuid.New().String()
|
||||||
|
}
|
||||||
|
authToken, err := token.CreateAuthToken(gc, user, roles, scope, loginMethod, nonce, code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Failed to create auth token: ", err)
|
log.Debug("Failed to create auth token: ", err)
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Code challenge could be optional if PKCE flow is not used
|
||||||
|
if code != "" {
|
||||||
|
if err := memorystore.Provider.SetState(code, codeChallenge+"@@"+authToken.FingerPrintHash); err != nil {
|
||||||
|
log.Debug("Failed to set code state: ", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
db.Provider.DeleteOTP(gc, otp)
|
db.Provider.DeleteOTP(gc, otp)
|
||||||
if isSignUp {
|
if isSignUp {
|
||||||
|
|
|
@ -51,7 +51,8 @@ func validateJwtTokenTest(t *testing.T, s TestSetup) {
|
||||||
gc, err := utils.GinContextFromContext(ctx)
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
sessionKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID
|
sessionKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID
|
||||||
authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth)
|
nonce := uuid.New().String()
|
||||||
|
authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce, "")
|
||||||
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
|
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
|
||||||
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
|
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package token
|
package token
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -10,7 +12,6 @@ import (
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
@ -47,30 +48,9 @@ type SessionData struct {
|
||||||
LoginMethod string `json:"login_method"`
|
LoginMethod string `json:"login_method"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateSessionToken creates a new session token
|
|
||||||
func CreateSessionToken(user models.User, nonce string, roles, scope []string, loginMethod string) (*SessionData, string, error) {
|
|
||||||
fingerPrintMap := &SessionData{
|
|
||||||
Nonce: nonce,
|
|
||||||
Roles: roles,
|
|
||||||
Subject: user.ID,
|
|
||||||
Scope: scope,
|
|
||||||
LoginMethod: loginMethod,
|
|
||||||
IssuedAt: time.Now().Unix(),
|
|
||||||
ExpiresAt: time.Now().AddDate(1, 0, 0).Unix(),
|
|
||||||
}
|
|
||||||
fingerPrintBytes, _ := json.Marshal(fingerPrintMap)
|
|
||||||
fingerPrintHash, err := crypto.EncryptAES(string(fingerPrintBytes))
|
|
||||||
if err != nil {
|
|
||||||
return nil, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fingerPrintMap, fingerPrintHash, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateAuthToken creates a new auth token when userlogs in
|
// CreateAuthToken creates a new auth token when userlogs in
|
||||||
func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, loginMethod string) (*Token, error) {
|
func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, loginMethod, nonce string, code string) (*Token, error) {
|
||||||
hostname := parsers.GetHost(gc)
|
hostname := parsers.GetHost(gc)
|
||||||
nonce := uuid.New().String()
|
|
||||||
_, fingerPrintHash, err := CreateSessionToken(user, nonce, roles, scope, loginMethod)
|
_, fingerPrintHash, err := CreateSessionToken(user, nonce, roles, scope, loginMethod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -80,7 +60,23 @@ func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, l
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
idToken, idTokenExpiresAt, err := CreateIDToken(user, roles, hostname, nonce, loginMethod)
|
atHash := sha256.New()
|
||||||
|
atHash.Write([]byte(accessToken))
|
||||||
|
atHashBytes := atHash.Sum(nil)
|
||||||
|
// hashedToken := string(bs)
|
||||||
|
atHashDigest := atHashBytes[0 : len(atHashBytes)/2]
|
||||||
|
atHashString := base64.RawURLEncoding.EncodeToString(atHashDigest)
|
||||||
|
|
||||||
|
codeHashString := ""
|
||||||
|
if code != "" {
|
||||||
|
codeHash := sha256.New()
|
||||||
|
codeHash.Write([]byte(code))
|
||||||
|
codeHashBytes := codeHash.Sum(nil)
|
||||||
|
codeHashDigest := codeHashBytes[0 : len(codeHashBytes)/2]
|
||||||
|
codeHashString = base64.RawURLEncoding.EncodeToString(codeHashDigest)
|
||||||
|
}
|
||||||
|
|
||||||
|
idToken, idTokenExpiresAt, err := CreateIDToken(user, roles, hostname, nonce, atHashString, codeHashString, loginMethod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -104,6 +100,26 @@ func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string, l
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateSessionToken creates a new session token
|
||||||
|
func CreateSessionToken(user models.User, nonce string, roles, scope []string, loginMethod string) (*SessionData, string, error) {
|
||||||
|
fingerPrintMap := &SessionData{
|
||||||
|
Nonce: nonce,
|
||||||
|
Roles: roles,
|
||||||
|
Subject: user.ID,
|
||||||
|
Scope: scope,
|
||||||
|
LoginMethod: loginMethod,
|
||||||
|
IssuedAt: time.Now().Unix(),
|
||||||
|
ExpiresAt: time.Now().AddDate(1, 0, 0).Unix(),
|
||||||
|
}
|
||||||
|
fingerPrintBytes, _ := json.Marshal(fingerPrintMap)
|
||||||
|
fingerPrintHash, err := crypto.EncryptAES(string(fingerPrintBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fingerPrintMap, fingerPrintHash, nil
|
||||||
|
}
|
||||||
|
|
||||||
// CreateRefreshToken util to create JWT token
|
// CreateRefreshToken util to create JWT token
|
||||||
func CreateRefreshToken(user models.User, roles, scopes []string, hostname, nonce, loginMethod string) (string, int64, error) {
|
func CreateRefreshToken(user models.User, roles, scopes []string, hostname, nonce, loginMethod string) (string, int64, error) {
|
||||||
// expires in 1 year
|
// expires in 1 year
|
||||||
|
@ -318,7 +334,9 @@ func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionD
|
||||||
|
|
||||||
// CreateIDToken util to create JWT token, based on
|
// CreateIDToken util to create JWT token, based on
|
||||||
// user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT
|
// user information, roles config and CUSTOM_ACCESS_TOKEN_SCRIPT
|
||||||
func CreateIDToken(user models.User, roles []string, hostname, nonce, loginMethod string) (string, int64, error) {
|
// For response_type (code) / authorization_code grant nonce should be empty
|
||||||
|
// for implicit flow it should be present to verify with actual state
|
||||||
|
func CreateIDToken(user models.User, roles []string, hostname, nonce, atHash, cHash, loginMethod string) (string, int64, error) {
|
||||||
expireTime, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAccessTokenExpiryTime)
|
expireTime, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAccessTokenExpiryTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", 0, err
|
return "", 0, err
|
||||||
|
@ -344,10 +362,10 @@ func CreateIDToken(user models.User, roles []string, hostname, nonce, loginMetho
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", 0, err
|
return "", 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
customClaims := jwt.MapClaims{
|
customClaims := jwt.MapClaims{
|
||||||
"iss": hostname,
|
"iss": hostname,
|
||||||
"aud": clientID,
|
"aud": clientID,
|
||||||
"nonce": nonce,
|
|
||||||
"sub": user.ID,
|
"sub": user.ID,
|
||||||
"exp": expiresAt,
|
"exp": expiresAt,
|
||||||
"iat": time.Now().Unix(),
|
"iat": time.Now().Unix(),
|
||||||
|
@ -357,6 +375,16 @@ func CreateIDToken(user models.User, roles []string, hostname, nonce, loginMetho
|
||||||
claimKey: roles,
|
claimKey: roles,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// split nonce to see if its authorization code grant method
|
||||||
|
|
||||||
|
if cHash != "" {
|
||||||
|
customClaims["at_hash"] = atHash
|
||||||
|
customClaims["c_hash"] = cHash
|
||||||
|
} else {
|
||||||
|
customClaims["nonce"] = nonce
|
||||||
|
customClaims["at_hash"] = atHash
|
||||||
|
}
|
||||||
|
|
||||||
for k, v := range userMap {
|
for k, v := range userMap {
|
||||||
if k != "roles" {
|
if k != "roles" {
|
||||||
customClaims[k] = v
|
customClaims[k] = v
|
||||||
|
|
|
@ -7,8 +7,6 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO re-name GinContextKey -> GinContext
|
|
||||||
|
|
||||||
// GinContext to get gin context from context
|
// GinContext to get gin context from context
|
||||||
func GinContextFromContext(ctx context.Context) (*gin.Context, error) {
|
func GinContextFromContext(ctx context.Context) (*gin.Context, error) {
|
||||||
ginContext := ctx.Value("GinContextKey")
|
ginContext := ctx.Value("GinContextKey")
|
||||||
|
|
13
templates/authorize_form_post.tmpl
Normal file
13
templates/authorize_form_post.tmpl
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Authorization Response</title>
|
||||||
|
</head>
|
||||||
|
<body onload="document.forms['authorize_form_post'].submit()">
|
||||||
|
<form action="{{.target_origin}}" name="authorize_form_post" method="POST">
|
||||||
|
{{ range $key, $val := .authorization_response }}
|
||||||
|
<input type="hidden" key="{{$key}}" value="{{$val}}" name="{{$key}}" id="{{$key}}" />
|
||||||
|
{{ end }}
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user