fix: add token information in redirect url

This commit is contained in:
Lakhan Samani 2022-03-08 12:36:26 +05:30
parent 57bc091499
commit 8c2bf6ee0d
26 changed files with 440 additions and 225 deletions

30
app/package-lock.json generated
View File

@ -9,7 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@authorizerdev/authorizer-react": "0.8.0", "@authorizerdev/authorizer-react": "0.9.0-beta.0",
"@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",
@ -24,9 +24,9 @@
} }
}, },
"node_modules/@authorizerdev/authorizer-js": { "node_modules/@authorizerdev/authorizer-js": {
"version": "0.3.0", "version": "0.4.0-beta.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.3.0.tgz", "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.4.0-beta.0.tgz",
"integrity": "sha512-KCE5Dw5MUnEgstBUayBriDQAOjqbxU7ixC00rTHAE6aD6TxJkeSls0vCTXpvt4iiKhFK6q9BhHwa/5NwWYpDBQ==", "integrity": "sha512-wNh5ROldNqdbOXFPDlq1tObzPZyEQkbnOvSEwvnDfPYb9/BsJ3naj3/ayz4J2R5k2+Eyuk0LK64XYdkfIW0HYA==",
"dependencies": { "dependencies": {
"node-fetch": "^2.6.1" "node-fetch": "^2.6.1"
}, },
@ -35,11 +35,11 @@
} }
}, },
"node_modules/@authorizerdev/authorizer-react": { "node_modules/@authorizerdev/authorizer-react": {
"version": "0.8.0", "version": "0.9.0-beta.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.8.0.tgz", "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.0.tgz",
"integrity": "sha512-178XWGEPsovy3f6Yi2Llh6kFmjdf3ZrkIsqIAKEGPhZawV/1sA6v+4FZp7ReuCxsCelckFFQUnPR8P7od+2HeA==", "integrity": "sha512-+I0JGobxuTDwKPAqwkJuczXnQ+ooNnz9IrfQPl8ZHnKoZsRqSSPCOLO6o0BLBu65h6pWZdLEEFN8CjRnu1+Zuw==",
"dependencies": { "dependencies": {
"@authorizerdev/authorizer-js": "^0.3.0", "@authorizerdev/authorizer-js": "^0.4.0-beta.0",
"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"
@ -829,19 +829,19 @@
}, },
"dependencies": { "dependencies": {
"@authorizerdev/authorizer-js": { "@authorizerdev/authorizer-js": {
"version": "0.3.0", "version": "0.4.0-beta.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.3.0.tgz", "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.4.0-beta.0.tgz",
"integrity": "sha512-KCE5Dw5MUnEgstBUayBriDQAOjqbxU7ixC00rTHAE6aD6TxJkeSls0vCTXpvt4iiKhFK6q9BhHwa/5NwWYpDBQ==", "integrity": "sha512-wNh5ROldNqdbOXFPDlq1tObzPZyEQkbnOvSEwvnDfPYb9/BsJ3naj3/ayz4J2R5k2+Eyuk0LK64XYdkfIW0HYA==",
"requires": { "requires": {
"node-fetch": "^2.6.1" "node-fetch": "^2.6.1"
} }
}, },
"@authorizerdev/authorizer-react": { "@authorizerdev/authorizer-react": {
"version": "0.8.0", "version": "0.9.0-beta.0",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.8.0.tgz", "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.0.tgz",
"integrity": "sha512-178XWGEPsovy3f6Yi2Llh6kFmjdf3ZrkIsqIAKEGPhZawV/1sA6v+4FZp7ReuCxsCelckFFQUnPR8P7od+2HeA==", "integrity": "sha512-+I0JGobxuTDwKPAqwkJuczXnQ+ooNnz9IrfQPl8ZHnKoZsRqSSPCOLO6o0BLBu65h6pWZdLEEFN8CjRnu1+Zuw==",
"requires": { "requires": {
"@authorizerdev/authorizer-js": "^0.3.0", "@authorizerdev/authorizer-js": "^0.4.0-beta.0",
"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"

View File

@ -11,7 +11,7 @@
"author": "Lakhan Samani", "author": "Lakhan Samani",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@authorizerdev/authorizer-react": "latest", "@authorizerdev/authorizer-react": "0.9.0-beta.0",
"@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",

View File

@ -6,9 +6,6 @@ import Root from './Root';
export default function App() { export default function App() {
// @ts-ignore // @ts-ignore
const globalState: Record<string, string> = window['__authorizer__']; const globalState: Record<string, string> = window['__authorizer__'];
if (globalState.state) {
sessionStorage.setItem('authorizer_state', globalState.state);
}
return ( return (
<div <div
style={{ style={{
@ -33,15 +30,7 @@ export default function App() {
/> />
<h1>{globalState.organizationName}</h1> <h1>{globalState.organizationName}</h1>
</div> </div>
<div <div className="container">
style={{
width: 400,
margin: `10px auto`,
border: `1px solid #D1D5DB`,
padding: `25px 20px`,
borderRadius: 5,
}}
>
<BrowserRouter> <BrowserRouter>
<AuthorizerProvider <AuthorizerProvider
config={{ config={{

View File

@ -11,14 +11,19 @@ export default function Root() {
useEffect(() => { useEffect(() => {
if (token) { if (token) {
const state = sessionStorage.getItem('authorizer_state')?.trim(); console.log({ token });
const url = new URL(config.redirectURL || '/app'); let redirectURL = config.redirectURL || '/app';
const params = `access_token=${token.access_token}&id_token=${token.id_token}&expires_in=${token.expires_in}&refresh_token=${token.refresh_token}`;
const url = new URL(redirectURL);
if (redirectURL.includes('?')) {
redirectURL = `${redirectURL}&${params}`;
} else {
redirectURL = `${redirectURL}?${params}`;
}
if (url.origin !== window.location.origin) { if (url.origin !== window.location.origin) {
console.log({ x: `${config.redirectURL || '/app'}?state=${state}` });
sessionStorage.removeItem('authorizer_state'); sessionStorage.removeItem('authorizer_state');
window.location.replace( window.location.replace(redirectURL);
`${config.redirectURL || '/app'}?state=${state}`
);
} }
} }
return () => {}; return () => {};

View File

@ -1,5 +1,5 @@
body { body {
margin: 0; margin: 10;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif; sans-serif;
@ -14,3 +14,17 @@ body {
*:after { *:after {
box-sizing: inherit; box-sizing: inherit;
} }
.container {
box-sizing: content-box;
border: 1px solid #d1d5db;
padding: 25px 20px;
border-radius: 5px;
}
@media only screen and (min-width: 768px) {
.container {
width: 400px;
margin: 0 auto;
}
}

View File

@ -13,6 +13,7 @@ type VerificationRequest struct {
UpdatedAt int64 `json:"updated_at" bson:"updated_at"` UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email"` Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email"`
Nonce string `gorm:"type:char(36)" json:"nonce" bson:"nonce"` Nonce string `gorm:"type:char(36)" json:"nonce" bson:"nonce"`
RedirectURI string `gorm:"type:text" json:"redirect_uri" bson:"redirect_uri"`
} }
func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest { func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequest {
@ -24,5 +25,7 @@ func (v *VerificationRequest) AsAPIVerificationRequest() *model.VerificationRequ
CreatedAt: &v.CreatedAt, CreatedAt: &v.CreatedAt,
UpdatedAt: &v.UpdatedAt, UpdatedAt: &v.UpdatedAt,
Email: &v.Email, Email: &v.Email,
Nonce: &v.Nonce,
RedirectURI: &v.RedirectURI,
} }
} }

View File

@ -178,6 +178,8 @@ type ComplexityRoot struct {
Expires func(childComplexity int) int Expires func(childComplexity int) int
ID func(childComplexity int) int ID func(childComplexity int) int
Identifier func(childComplexity int) int Identifier func(childComplexity int) int
Nonce func(childComplexity int) int
RedirectURI func(childComplexity int) int
Token func(childComplexity int) int Token func(childComplexity int) int
UpdatedAt func(childComplexity int) int UpdatedAt func(childComplexity int) int
} }
@ -1038,6 +1040,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.VerificationRequest.Identifier(childComplexity), true return e.complexity.VerificationRequest.Identifier(childComplexity), true
case "VerificationRequest.nonce":
if e.complexity.VerificationRequest.Nonce == nil {
break
}
return e.complexity.VerificationRequest.Nonce(childComplexity), true
case "VerificationRequest.redirect_uri":
if e.complexity.VerificationRequest.RedirectURI == nil {
break
}
return e.complexity.VerificationRequest.RedirectURI(childComplexity), true
case "VerificationRequest.token": case "VerificationRequest.token":
if e.complexity.VerificationRequest.Token == nil { if e.complexity.VerificationRequest.Token == nil {
break break
@ -1189,6 +1205,8 @@ type VerificationRequest {
expires: Int64 expires: Int64
created_at: Int64 created_at: Int64
updated_at: Int64 updated_at: Int64
nonce: String
redirect_uri: String
} }
type VerificationRequests { type VerificationRequests {
@ -1361,6 +1379,8 @@ input UpdateUserInput {
input ForgotPasswordInput { input ForgotPasswordInput {
email: String! email: String!
state: String
redirect_uri: String
} }
input ResetPasswordInput { input ResetPasswordInput {
@ -1377,6 +1397,8 @@ input MagicLinkLoginInput {
email: String! email: String!
roles: [String!] roles: [String!]
scope: [String!] scope: [String!]
state: String
redirect_uri: String
} }
input SessionQueryInput { input SessionQueryInput {
@ -5451,6 +5473,70 @@ func (ec *executionContext) _VerificationRequest_updated_at(ctx context.Context,
return ec.marshalOInt642ᚖint64(ctx, field.Selections, res) return ec.marshalOInt642ᚖint64(ctx, field.Selections, res)
} }
func (ec *executionContext) _VerificationRequest_nonce(ctx context.Context, field graphql.CollectedField, obj *model.VerificationRequest) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "VerificationRequest",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Nonce, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _VerificationRequest_redirect_uri(ctx context.Context, field graphql.CollectedField, obj *model.VerificationRequest) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "VerificationRequest",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.RedirectURI, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _VerificationRequests_pagination(ctx context.Context, field graphql.CollectedField, obj *model.VerificationRequests) (ret graphql.Marshaler) { func (ec *executionContext) _VerificationRequests_pagination(ctx context.Context, field graphql.CollectedField, obj *model.VerificationRequests) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -6729,6 +6815,22 @@ func (ec *executionContext) unmarshalInputForgotPasswordInput(ctx context.Contex
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
}
case "redirect_uri":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("redirect_uri"))
it.RedirectURI, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
} }
} }
@ -6815,6 +6917,22 @@ func (ec *executionContext) unmarshalInputMagicLinkLoginInput(ctx context.Contex
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
}
case "redirect_uri":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("redirect_uri"))
it.RedirectURI, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
} }
} }
@ -8290,6 +8408,10 @@ func (ec *executionContext) _VerificationRequest(ctx context.Context, sel ast.Se
out.Values[i] = ec._VerificationRequest_created_at(ctx, field, obj) out.Values[i] = ec._VerificationRequest_created_at(ctx, field, obj)
case "updated_at": case "updated_at":
out.Values[i] = ec._VerificationRequest_updated_at(ctx, field, obj) out.Values[i] = ec._VerificationRequest_updated_at(ctx, field, obj)
case "nonce":
out.Values[i] = ec._VerificationRequest_nonce(ctx, field, obj)
case "redirect_uri":
out.Values[i] = ec._VerificationRequest_redirect_uri(ctx, field, obj)
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }

View File

@ -70,6 +70,8 @@ type Error struct {
type ForgotPasswordInput struct { type ForgotPasswordInput struct {
Email string `json:"email"` Email string `json:"email"`
State *string `json:"state"`
RedirectURI *string `json:"redirect_uri"`
} }
type LoginInput struct { type LoginInput struct {
@ -83,6 +85,8 @@ type MagicLinkLoginInput struct {
Email string `json:"email"` Email string `json:"email"`
Roles []string `json:"roles"` Roles []string `json:"roles"`
Scope []string `json:"scope"` Scope []string `json:"scope"`
State *string `json:"state"`
RedirectURI *string `json:"redirect_uri"`
} }
type Meta struct { type Meta struct {
@ -246,6 +250,8 @@ type VerificationRequest struct {
Expires *int64 `json:"expires"` Expires *int64 `json:"expires"`
CreatedAt *int64 `json:"created_at"` CreatedAt *int64 `json:"created_at"`
UpdatedAt *int64 `json:"updated_at"` UpdatedAt *int64 `json:"updated_at"`
Nonce *string `json:"nonce"`
RedirectURI *string `json:"redirect_uri"`
} }
type VerificationRequests struct { type VerificationRequests struct {

View File

@ -57,6 +57,8 @@ type VerificationRequest {
expires: Int64 expires: Int64
created_at: Int64 created_at: Int64
updated_at: Int64 updated_at: Int64
nonce: String
redirect_uri: String
} }
type VerificationRequests { type VerificationRequests {
@ -229,6 +231,8 @@ input UpdateUserInput {
input ForgotPasswordInput { input ForgotPasswordInput {
email: String! email: String!
state: String
redirect_uri: String
} }
input ResetPasswordInput { input ResetPasswordInput {
@ -245,6 +249,8 @@ input MagicLinkLoginInput {
email: String! email: String!
roles: [String!] roles: [String!]
scope: [String!] scope: [String!]
state: String
redirect_uri: String
} }
input SessionQueryInput { input SessionQueryInput {

View File

@ -1,13 +1,11 @@
package handlers package handlers
import ( import (
"encoding/json"
"log" "log"
"net/http" "net/http"
"strings" "strings"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -18,7 +16,6 @@ import (
type State struct { type State struct {
AuthorizerURL string `json:"authorizerURL"` AuthorizerURL string `json:"authorizerURL"`
RedirectURL string `json:"redirectURL"` RedirectURL string `json:"redirectURL"`
State string `json:"state"`
} }
// AppHandler is the handler for the /app route // AppHandler is the handler for the /app route
@ -30,44 +27,25 @@ func AppHandler() gin.HandlerFunc {
return return
} }
state := c.Query("state") redirect_uri := strings.TrimSpace(c.Query("redirect_uri"))
state := strings.TrimSpace(c.Query("state"))
scopeString := strings.TrimSpace(c.Query("scope"))
var stateObj State var scope []string
if scopeString == "" {
if state == "" { scope = []string{"openid", "profile", "email"}
stateObj.AuthorizerURL = hostname
stateObj.RedirectURL = hostname + "/app"
} else { } else {
decodedState, err := crypto.DecryptB64(state) scope = strings.Split(scopeString, " ")
if err != nil {
c.JSON(400, gin.H{"error": "[unable to decode state] invalid state"})
return
} }
err = json.Unmarshal([]byte(decodedState), &stateObj) if redirect_uri == "" {
if err != nil { redirect_uri = hostname + "/app"
c.JSON(400, gin.H{"error": "[unable to parse state] invalid state"}) } else {
return
}
stateObj.AuthorizerURL = strings.TrimSuffix(stateObj.AuthorizerURL, "/")
stateObj.RedirectURL = strings.TrimSuffix(stateObj.RedirectURL, "/")
// validate redirect url with allowed origins // validate redirect url with allowed origins
if !utils.IsValidOrigin(stateObj.RedirectURL) { if !utils.IsValidOrigin(redirect_uri) {
c.JSON(400, gin.H{"error": "invalid redirect url"}) c.JSON(400, gin.H{"error": "invalid redirect url"})
return return
} }
if stateObj.AuthorizerURL == "" {
c.JSON(400, gin.H{"error": "invalid authorizer url"})
return
}
// validate host and domain of authorizer url
if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != hostname {
c.JSON(400, gin.H{"error": "invalid host url"})
return
}
} }
// debug the request state // debug the request state
@ -78,10 +56,11 @@ func AppHandler() gin.HandlerFunc {
} }
} }
c.HTML(http.StatusOK, "app.tmpl", gin.H{ c.HTML(http.StatusOK, "app.tmpl", gin.H{
"data": map[string]string{ "data": map[string]interface{}{
"authorizerURL": stateObj.AuthorizerURL, "authorizerURL": hostname,
"redirectURL": stateObj.RedirectURL, "redirectURL": redirect_uri,
"state": stateObj.State, "scope": scope,
"state": state,
"organizationName": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName), "organizationName": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName),
"organizationLogo": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo), "organizationLogo": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo),
}, },

View File

@ -2,16 +2,15 @@ package handlers
import ( import (
"net/http" "net/http"
"strconv"
"strings" "strings"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/sessionstore" "github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/token" "github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
) )
@ -36,6 +35,13 @@ func AuthorizeHandler() gin.HandlerFunc {
template := "authorize.tmpl" template := "authorize.tmpl"
responseMode := strings.TrimSpace(gc.Query("response_mode")) responseMode := strings.TrimSpace(gc.Query("response_mode"))
var scope []string
if scopeString == "" {
scope = []string{"openid", "profile", "email"}
} else {
scope = strings.Split(scopeString, " ")
}
if responseMode == "" { if responseMode == "" {
responseMode = "query" responseMode = "query"
} }
@ -50,9 +56,7 @@ func AuthorizeHandler() gin.HandlerFunc {
isQuery := responseMode == "query" isQuery := responseMode == "query"
hostname := utils.GetHost(gc) loginURL := "/app?state=" + state + "&scope=" + strings.Join(scope, " ") + "&redirect_uri=" + redirectURI
loginRedirectState := crypto.EncryptB64(`{"authorizerURL":"` + hostname + `","redirectURL":"` + redirectURI + `", "state":"` + state + `"}`)
loginURL := "/app?state=" + loginRedirectState
if clientID == "" { if clientID == "" {
if isQuery { if isQuery {
@ -109,13 +113,6 @@ func AuthorizeHandler() gin.HandlerFunc {
responseType = "token" responseType = "token"
} }
var scope []string
if scopeString == "" {
scope = []string{"openid", "profile", "email"}
} else {
scope = strings.Split(scopeString, " ")
}
isResponseTypeCode := responseType == "code" isResponseTypeCode := responseType == "code"
isResponseTypeToken := responseType == "token" isResponseTypeToken := responseType == "token"
@ -279,8 +276,11 @@ func AuthorizeHandler() gin.HandlerFunc {
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
cookie.SetSession(gc, authToken.FingerPrintHash) cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := int64(1800) expiresIn := int64(1800)
// 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
res := map[string]interface{}{ res := map[string]interface{}{
"access_token": authToken.AccessToken.Token, "access_token": authToken.AccessToken.Token,
"id_token": authToken.IDToken.Token, "id_token": authToken.IDToken.Token,
@ -292,9 +292,17 @@ func AuthorizeHandler() gin.HandlerFunc {
if authToken.RefreshToken != nil { if authToken.RefreshToken != nil {
res["refresh_token"] = authToken.RefreshToken.Token res["refresh_token"] = authToken.RefreshToken.Token
params += "&refresh_token=" + authToken.RefreshToken.Token
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
} }
if isQuery {
if strings.Contains(redirectURI, "?") {
gc.Redirect(http.StatusFound, redirectURI+"&"+params)
} else {
gc.Redirect(http.StatusFound, redirectURI+"?"+params)
}
} else {
gc.HTML(http.StatusOK, template, gin.H{ gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI, "target_origin": redirectURI,
"authorization_response": map[string]interface{}{ "authorization_response": map[string]interface{}{
@ -302,6 +310,7 @@ func AuthorizeHandler() gin.HandlerFunc {
"response": res, "response": res,
}, },
}) })
}
return return
} }

View File

@ -7,6 +7,7 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"net/http" "net/http"
"strconv"
"strings" "strings"
"time" "time"
@ -21,7 +22,6 @@ import (
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"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"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
@ -37,16 +37,17 @@ func OAuthCallbackHandler() gin.HandlerFunc {
} }
sessionstore.GetState(state) sessionstore.GetState(state)
// contains random token, redirect url, role // contains random token, redirect url, role
sessionSplit := strings.Split(state, "___") sessionSplit := strings.Split(state, "@")
// TODO validate redirect url if len(sessionSplit) < 3 {
if len(sessionSplit) < 2 {
c.JSON(400, gin.H{"error": "invalid redirect url"}) c.JSON(400, gin.H{"error": "invalid redirect url"})
return return
} }
inputRoles := strings.Split(sessionSplit[2], ",") stateValue := sessionSplit[0]
redirectURL := sessionSplit[1] redirectURL := sessionSplit[1]
inputRoles := strings.Split(sessionSplit[2], ",")
scopes := strings.Split(sessionSplit[3], ",")
var err error var err error
user := models.User{} user := models.User{}
@ -145,17 +146,29 @@ func OAuthCallbackHandler() gin.HandlerFunc {
} }
} }
// TODO use query param authToken, err := token.CreateAuthToken(c, user, inputRoles, scopes)
scope := []string{"openid", "email", "profile"}
nonce := uuid.New().String()
_, newSessionToken, err := token.CreateSessionToken(user, nonce, inputRoles, scope)
if err != nil { if err != nil {
c.JSON(500, gin.H{"error": err.Error()}) c.JSON(500, gin.H{"error": err.Error()})
} }
expiresIn := int64(1800)
params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + stateValue + "&id_token=" + authToken.IDToken.Token
cookie.SetSession(c, authToken.FingerPrintHash)
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
if authToken.RefreshToken != nil {
params = params + `&refresh_token=${refresh_token}`
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
}
sessionstore.SetState(newSessionToken, nonce+"@"+user.ID)
cookie.SetSession(c, newSessionToken)
go utils.SaveSessionInDB(c, user.ID) go utils.SaveSessionInDB(c, user.ID)
if strings.Contains(redirectURL, "?") {
redirectURL = redirectURL + "&" + params
} else {
redirectURL = redirectURL + "?" + params
}
c.Redirect(http.StatusTemporaryRedirect, redirectURL) c.Redirect(http.StatusTemporaryRedirect, redirectURL)
} }
} }

View File

@ -10,23 +10,38 @@ import (
"github.com/authorizerdev/authorizer/server/sessionstore" "github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid"
) )
// OAuthLoginHandler set host in the oauth state that is useful for redirecting to oauth_callback // OAuthLoginHandler set host in the oauth state that is useful for redirecting to oauth_callback
func OAuthLoginHandler() gin.HandlerFunc { func OAuthLoginHandler() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
hostname := utils.GetHost(c) hostname := utils.GetHost(c)
redirectURL := c.Query("redirectURL") redirectURI := strings.TrimSpace(c.Query("redirectURL"))
roles := c.Query("roles") roles := strings.TrimSpace(c.Query("roles"))
state := strings.TrimSpace(c.Query("state"))
scopeString := strings.TrimSpace(c.Query("scope"))
if redirectURL == "" { if redirectURI == "" {
c.JSON(400, gin.H{ c.JSON(400, gin.H{
"error": "invalid redirect url", "error": "invalid redirect uri",
}) })
return return
} }
if state == "" {
c.JSON(400, gin.H{
"error": "invalid state",
})
return
}
var scope []string
if scopeString == "" {
scope = []string{"openid", "profile", "email"}
} else {
scope = strings.Split(scopeString, " ")
}
if roles != "" { if roles != "" {
// validate role // validate role
rolesSplit := strings.Split(roles, ",") rolesSplit := strings.Split(roles, ",")
@ -43,8 +58,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",") roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
} }
uuid := uuid.New() oauthStateString := state + "@" + redirectURI + "@" + roles + "@" + strings.Join(scope, ",")
oauthStateString := uuid.String() + "___" + redirectURL + "___" + roles
provider := c.Param("oauth_provider") provider := c.Param("oauth_provider")
isProviderConfigured := true isProviderConfigured := true

View File

@ -2,6 +2,7 @@ package handlers
import ( import (
"net/http" "net/http"
"strconv"
"strings" "strings"
"time" "time"
@ -11,7 +12,6 @@ import (
"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/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid"
) )
// VerifyEmailHandler handles the verify email route. // VerifyEmailHandler handles the verify email route.
@ -19,7 +19,7 @@ import (
func VerifyEmailHandler() gin.HandlerFunc { func VerifyEmailHandler() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
errorRes := gin.H{ errorRes := gin.H{
"error": "invalid token", "error": "invalid_token",
} }
tokenInQuery := c.Query("token") tokenInQuery := c.Query("token")
if tokenInQuery == "" { if tokenInQuery == "" {
@ -29,30 +29,24 @@ func VerifyEmailHandler() gin.HandlerFunc {
verificationRequest, err := db.Provider.GetVerificationRequestByToken(tokenInQuery) verificationRequest, err := db.Provider.GetVerificationRequestByToken(tokenInQuery)
if err != nil { if err != nil {
errorRes["error_description"] = err.Error()
c.JSON(400, errorRes) c.JSON(400, errorRes)
return return
} }
// verify if token exists in db // verify if token exists in db
hostname := utils.GetHost(c) hostname := utils.GetHost(c)
encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce) claim, err := token.ParseJWTToken(tokenInQuery, hostname, verificationRequest.Nonce, verificationRequest.Email)
if err != nil {
c.JSON(400, gin.H{
"error": err.Error(),
})
return
}
claim, err := token.ParseJWTToken(tokenInQuery, hostname, encryptedNonce, verificationRequest.Email)
if err != nil { if err != nil {
errorRes["error_description"] = err.Error()
c.JSON(400, errorRes) c.JSON(400, errorRes)
return return
} }
user, err := db.Provider.GetUserByEmail(claim["sub"].(string)) user, err := db.Provider.GetUserByEmail(claim["sub"].(string))
if err != nil { if err != nil {
c.JSON(400, gin.H{ errorRes["error_description"] = err.Error()
"message": err.Error(), c.JSON(400, errorRes)
})
return return
} }
@ -65,21 +59,53 @@ func VerifyEmailHandler() gin.HandlerFunc {
// delete from verification table // delete from verification table
db.Provider.DeleteVerificationRequest(verificationRequest) db.Provider.DeleteVerificationRequest(verificationRequest)
roles := strings.Split(user.Roles, ",") state := strings.TrimSpace(c.Query("state"))
scope := []string{"openid", "email", "profile"} redirectURL := strings.TrimSpace(c.Query("redirect_uri"))
nonce := uuid.New().String() rolesString := strings.TrimSpace(c.Query("roles"))
_, authToken, err := token.CreateSessionToken(user, nonce, roles, scope) var roles []string
if rolesString == "" {
roles = strings.Split(user.Roles, ",")
} else {
roles = strings.Split(rolesString, ",")
}
scopeString := strings.TrimSpace(c.Query("scope"))
var scope []string
if scopeString == "" {
scope = []string{"openid", "email", "profile"}
} else {
scope = strings.Split(scopeString, " ")
}
authToken, err := token.CreateAuthToken(c, user, roles, scope)
if err != nil { if err != nil {
c.JSON(400, gin.H{ errorRes["error_description"] = err.Error()
"message": err.Error(), c.JSON(500, errorRes)
})
return return
} }
sessionstore.SetState(authToken, nonce+"@"+user.ID) expiresIn := int64(1800)
cookie.SetSession(c, authToken) params := "access_token=" + authToken.AccessToken.Token + "&token_type=bearer&expires_in=" + strconv.FormatInt(expiresIn, 10) + "&state=" + state + "&id_token=" + authToken.IDToken.Token
cookie.SetSession(c, authToken.FingerPrintHash)
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
if authToken.RefreshToken != nil {
params = params + `&refresh_token=${refresh_token}`
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
}
if redirectURL == "" {
redirectURL = claim["redirect_url"].(string)
}
if strings.Contains(redirectURL, "?") {
redirectURL = redirectURL + "&" + params
} else {
redirectURL = redirectURL + "?" + params
}
go utils.SaveSessionInDB(c, user.ID) go utils.SaveSessionInDB(c, user.ID)
c.Redirect(http.StatusTemporaryRedirect, claim["redirect_url"].(string)) c.Redirect(http.StatusTemporaryRedirect, redirectURL)
} }
} }

View File

@ -39,11 +39,16 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
} }
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
nonce, nonceHash, err := utils.GenerateNonce() _, nonceHash, err := utils.GenerateNonce()
if err != nil { if err != nil {
return res, err return res, err
} }
verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname, nonceHash) redirectURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL)
if params.RedirectURI != nil {
redirectURL = *params.RedirectURI
}
verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname, nonceHash, redirectURL)
if err != nil { if err != nil {
log.Println(`error generating token`, err) log.Println(`error generating token`, err)
} }
@ -52,7 +57,8 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
Identifier: constants.VerificationTypeForgotPassword, Identifier: constants.VerificationTypeForgotPassword,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: params.Email, Email: params.Email,
Nonce: nonce, Nonce: nonceHash,
RedirectURI: redirectURL,
}) })
// exec it as go routin so that we can reduce the api latency // exec it as go routin so that we can reduce the api latency

View File

@ -69,8 +69,6 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
return res, err return res, err
} }
cookie.SetSession(gc, authToken.FingerPrintHash)
expiresIn := int64(1800) expiresIn := int64(1800)
res = &model.AuthResponse{ res = &model.AuthResponse{
Message: `Logged in successfully`, Message: `Logged in successfully`,
@ -80,6 +78,7 @@ func LoginResolver(ctx context.Context, params model.LoginInput) (*model.AuthRes
User: user.AsAPIUser(), User: user.AsAPIUser(),
} }
cookie.SetSession(gc, authToken.FingerPrintHash)
sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID) sessionstore.SetState(authToken.FingerPrintHash, authToken.FingerPrint+"@"+user.ID)
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID) sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)

View File

@ -68,6 +68,9 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
// Need to modify roles in this case // Need to modify roles in this case
// find the unassigned roles // find the unassigned roles
if len(params.Roles) <= 0 {
inputRoles = envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles)
}
existingRoles := strings.Split(existingUser.Roles, ",") existingRoles := strings.Split(existingUser.Roles, ",")
unasignedRoles := []string{} unasignedRoles := []string{}
for _, ir := range inputRoles { for _, ir := range inputRoles {
@ -109,12 +112,30 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) { if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
// insert verification request // insert verification request
nonce, nonceHash, err := utils.GenerateNonce() _, nonceHash, err := utils.GenerateNonce()
if err != nil { if err != nil {
return res, err return res, err
} }
redirectURLParams := "&roles=" + strings.Join(inputRoles, ",")
if params.State != nil {
redirectURLParams = redirectURLParams + "&state=" + *params.State
}
if params.Scope != nil && len(params.Scope) > 0 {
redirectURLParams = redirectURLParams + "&scope=" + strings.Join(params.Scope, " ")
}
redirectURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL)
if params.RedirectURI != nil {
redirectURL = *params.RedirectURI
}
if strings.Contains(redirectURL, "?") {
redirectURL = redirectURL + "&" + redirectURLParams
} else {
redirectURL = redirectURL + "?" + redirectURLParams
}
verificationType := constants.VerificationTypeMagicLinkLogin verificationType := constants.VerificationTypeMagicLinkLogin
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname, nonceHash) verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname, nonceHash, redirectURL)
if err != nil { if err != nil {
log.Println(`error generating token`, err) log.Println(`error generating token`, err)
} }
@ -123,7 +144,8 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
Identifier: verificationType, Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: params.Email, Email: params.Email,
Nonce: nonce, Nonce: nonceHash,
RedirectURI: redirectURL,
}) })
// exec it as go routin so that we can reduce the api latency // exec it as go routin so that we can reduce the api latency

View File

@ -44,11 +44,12 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
} }
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
nonce, nonceHash, err := utils.GenerateNonce() _, nonceHash, err := utils.GenerateNonce()
if err != nil { if err != nil {
return res, err return res, err
} }
verificationToken, err := token.CreateVerificationToken(params.Email, params.Identifier, hostname, nonceHash)
verificationToken, err := token.CreateVerificationToken(params.Email, params.Identifier, hostname, nonceHash, verificationRequest.RedirectURI)
if err != nil { if err != nil {
log.Println(`error generating token`, err) log.Println(`error generating token`, err)
} }
@ -57,7 +58,8 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
Identifier: params.Identifier, Identifier: params.Identifier,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: params.Email, Email: params.Email,
Nonce: nonce, Nonce: nonceHash,
RedirectURI: verificationRequest.RedirectURI,
}) })
// exec it as go routin so that we can reduce the api latency // exec it as go routin so that we can reduce the api latency

View File

@ -37,11 +37,7 @@ func ResetPasswordResolver(ctx context.Context, params model.ResetPasswordInput)
// verify if token exists in db // verify if token exists in db
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce) claim, err := token.ParseJWTToken(params.Token, hostname, verificationRequest.Nonce, verificationRequest.Email)
if err != nil {
return res, err
}
claim, err := token.ParseJWTToken(params.Token, hostname, encryptedNonce, verificationRequest.Email)
if err != nil { if err != nil {
return res, fmt.Errorf(`invalid token`) return res, fmt.Errorf(`invalid token`)
} }

View File

@ -123,12 +123,13 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) { if !envstore.EnvStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
// insert verification request // insert verification request
nonce, nonceHash, err := utils.GenerateNonce() _, nonceHash, err := utils.GenerateNonce()
if err != nil { if err != nil {
return res, err return res, err
} }
verificationType := constants.VerificationTypeBasicAuthSignup verificationType := constants.VerificationTypeBasicAuthSignup
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname, nonceHash) redirectURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL)
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname, nonceHash, redirectURL)
if err != nil { if err != nil {
return res, err return res, err
} }
@ -137,7 +138,8 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
Identifier: verificationType, Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: params.Email, Email: params.Email,
Nonce: nonce, Nonce: nonceHash,
RedirectURI: redirectURL,
}) })
// exec it as go routin so that we can reduce the api latency // exec it as go routin so that we can reduce the api latency

View File

@ -129,12 +129,13 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
user.EmailVerifiedAt = nil user.EmailVerifiedAt = nil
hasEmailChanged = true hasEmailChanged = true
// insert verification request // insert verification request
nonce, nonceHash, err := utils.GenerateNonce() _, nonceHash, err := utils.GenerateNonce()
if err != nil { if err != nil {
return res, err return res, err
} }
verificationType := constants.VerificationTypeUpdateEmail verificationType := constants.VerificationTypeUpdateEmail
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname, nonceHash) redirectURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL)
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname, nonceHash, redirectURL)
if err != nil { if err != nil {
log.Println(`error generating token`, err) log.Println(`error generating token`, err)
} }
@ -143,7 +144,8 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
Identifier: verificationType, Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: newEmail, Email: newEmail,
Nonce: nonce, Nonce: nonceHash,
RedirectURI: redirectURL,
}) })
// exec it as go routin so that we can reduce the api latency // exec it as go routin so that we can reduce the api latency

View File

@ -101,12 +101,13 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
user.Email = newEmail user.Email = newEmail
user.EmailVerifiedAt = nil user.EmailVerifiedAt = nil
// insert verification request // insert verification request
nonce, nonceHash, err := utils.GenerateNonce() _, nonceHash, err := utils.GenerateNonce()
if err != nil { if err != nil {
return res, err return res, err
} }
verificationType := constants.VerificationTypeUpdateEmail verificationType := constants.VerificationTypeUpdateEmail
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname, nonceHash) redirectURL := envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL)
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname, nonceHash, redirectURL)
if err != nil { if err != nil {
log.Println(`error generating token`, err) log.Println(`error generating token`, err)
} }
@ -115,7 +116,8 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
Identifier: verificationType, Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: newEmail, Email: newEmail,
Nonce: nonce, Nonce: nonceHash,
RedirectURI: redirectURL,
}) })
// exec it as go routin so that we can reduce the api latency // exec it as go routin so that we can reduce the api latency

View File

@ -29,11 +29,7 @@ func VerifyEmailResolver(ctx context.Context, params model.VerifyEmailInput) (*m
// verify if token exists in db // verify if token exists in db
hostname := utils.GetHost(gc) hostname := utils.GetHost(gc)
encryptedNonce, err := utils.EncryptNonce(verificationRequest.Nonce) claim, err := token.ParseJWTToken(params.Token, hostname, verificationRequest.Nonce, verificationRequest.Email)
if err != nil {
return res, err
}
claim, err := token.ParseJWTToken(params.Token, hostname, encryptedNonce, verificationRequest.Email)
if err != nil { if err != nil {
return res, fmt.Errorf(`invalid token: %s`, err.Error()) return res, fmt.Errorf(`invalid token: %s`, err.Error())
} }

View File

@ -2,6 +2,7 @@ package token
import ( import (
"errors" "errors"
"fmt"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/crypto"
@ -91,6 +92,7 @@ func ParseJWTToken(token, hostname, nonce, subject string) (jwt.MapClaims, error
return claims, errors.New("invalid audience") return claims, errors.New("invalid audience")
} }
fmt.Println("claims:", claims, claims["nonce"], nonce)
if claims["nonce"] != nonce { if claims["nonce"] != nonce {
return claims, errors.New("invalid nonce") return claims, errors.New("invalid nonce")
} }

View File

@ -9,7 +9,7 @@ import (
) )
// CreateVerificationToken creates a verification JWT token // CreateVerificationToken creates a verification JWT token
func CreateVerificationToken(email, tokenType, hostname, nonceHash string) (string, error) { func CreateVerificationToken(email, tokenType, hostname, nonceHash, redirectURL string) (string, error) {
claims := jwt.MapClaims{ claims := jwt.MapClaims{
"iss": hostname, "iss": hostname,
"aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID), "aud": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID),
@ -18,7 +18,7 @@ func CreateVerificationToken(email, tokenType, hostname, nonceHash string) (stri
"iat": time.Now().Unix(), "iat": time.Now().Unix(),
"token_type": tokenType, "token_type": tokenType,
"nonce": nonceHash, "nonce": nonceHash,
"redirect_url": envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL), "redirect_url": redirectURL,
} }
return SignJWTToken(claims) return SignJWTToken(claims)

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>{{.data.organizationName}}</title> <title>{{.data.organizationName}}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="apple-touch-icon" sizes="180x180" href="/app/favicon_io/apple-touch-icon.png"> <link rel="apple-touch-icon" sizes="180x180" href="/app/favicon_io/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/app/favicon_io/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="32x32" href="/app/favicon_io/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/app/favicon_io/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="/app/favicon_io/favicon-16x16.png">