Skip to content
Snippets Groups Projects
Commit d3d26e8d authored by Nicolas Pernoud's avatar Nicolas Pernoud
Browse files

feat: fully functionning mandatement

parent 925ca5ee
Branches
No related tags found
No related merge requests found
[ ] Remove references to localhost and ports
[ ] Tests
[ ] Remove useless fmt.Print
[ ] Obviously : TODOs
[ ] Css and JS messages
......@@ -4,6 +4,9 @@ go 1.16
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mattn/go-sqlite3 v1.14.7 // indirect
github.com/oschwald/maxminddb-golang v1.8.0
golang.org/x/sys v0.0.0-20210608053332-aa57babbf139 // indirect
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71 // indirect
gorm.io/driver/sqlite v1.1.4
gorm.io/gorm v1.21.10
)
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/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/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA=
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/oschwald/maxminddb-golang v1.8.0 h1:Uh/DSnGoxsyp/KYbY1AuP0tYEwfs0sCph9p/UMXK/Hk=
github.com/oschwald/maxminddb-golang v1.8.0/go.mod h1:RXZtst0N6+FY/3qCNmZMBApR19cdQj43/NM9VkrNAis=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
......@@ -9,8 +17,13 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210608053332-aa57babbf139 h1:C+AwYEtBp/VQwoLntUmQ/yx3MS9vmZaKNdw5eOpoQe8=
golang.org/x/sys v0.0.0-20210608053332-aa57babbf139/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71 h1:X/2sJAybVknnUnV7AD2HdT6rm2p5BP6eH2j+igduWgk=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.21.10 h1:kBGiBsaqOQ+8f6S2U6mvGFz6aWWyCeIiuaFcaBozp4M=
gorm.io/gorm v1.21.10/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
package mandate
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
type Mandate struct {
gorm.Model
Sirent string
MandateeSub string
MandaterSub string
// TODO : Expiration date
}
var db *gorm.DB
func init() {
var err error
db, err = gorm.Open(sqlite.Open("glcpro.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// Migrate the schema
db.AutoMigrate(&Mandate{})
}
func Exists(sirent string, mandateeSub string) bool {
var mandate Mandate
if err := db.Where("sirent = ? AND mandatee_sub = ?", sirent, mandateeSub).First(&mandate).Error; err != nil {
return false
}
return true
}
func Create(sirent string, mandateeSub string, mandaterSub string) {
db.Create(&Mandate{Sirent: sirent, MandateeSub: mandateeSub, MandaterSub: mandaterSub})
}
package mandate
import (
"testing"
)
func TestExists(t *testing.T) {
Create("001", "101", "201")
type args struct {
sirent string
mandateeSub string
}
tests := []struct {
name string
args args
want bool
}{
{"exists", args{sirent: "001", mandateeSub: "101"}, true},
{"bad_mandatee", args{sirent: "001", mandateeSub: "102"}, false},
{"bad_sirent", args{sirent: "002", mandateeSub: "101"}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Exists(tt.args.sirent, tt.args.mandateeSub); got != tt.want {
t.Errorf("Exists() = %v, want %v", got, tt.want)
}
})
}
}
......@@ -7,8 +7,12 @@
l'entreprise ou de l'établissement portant le numéro SIREN/SIRET
{{.Sirent}}.
</p>
<p>Pour valider cette demande, veuillez cliquer sur ce lien :</p>
<a href="localhost:8080/api/matcher/validate&code={{.Token}}">Valider</a>
<p>
Pour valider cette demande, veuillez cliquer sur ce lien :
<a href="http://localhost:8080/api/matcher/validate?code={{.Token}}"
>Valider.</a
>
</p>
<p>
Pour refuser cette demande, il suffit de ne rien faire et de supprimer ce
mail.
......
package franceconnect
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
)
type Identity struct {
GivenName string `json:"given_name"`
FamilyName string `json:"family_name"`
Birthdate string `json:"birthdate"`
Gender string `json:"gender"`
Birthplace string `json:"birthplace"`
Birthcountry string `json:"birthcountry"`
PreferredUsername string `json:"preferred_username"`
Sub string `json:"sub"`
}
func RedirectToFranceConnect(w http.ResponseWriter, r *http.Request) {
// TODO : France Connect parameters as env variables
fcAuth := "https://fcp.integ01.dev-franceconnect.fr/api/v1/authorize"
fcClientID := "211286433e39cce01db448d80181bdfd005554b19cd51b3fe7943f6b3b86ab6e"
params := url.Values{}
// TODO : State and nonce
params.Add("state", "A_RANDOM_STATE")
params.Add("nonce", "A_RANDOM_NONCE")
params.Add("scope", "openid identite_pivot")
// TODO : Redirect from request
params.Add("redirect_uri", "http://localhost:8080"+"/callback")
params.Add("client_id", fcClientID)
params.Add("response_type", "code")
http.Redirect(w, r, fcAuth+"?"+params.Encode(), http.StatusFound)
}
func HandleCallBack(w http.ResponseWriter, r *http.Request) (Identity, error) {
// TODO : fcToken and client secret from env
fcToken := "https://fcp.integ01.dev-franceconnect.fr/api/v1/token"
fcUserInfo := "https://fcp.integ01.dev-franceconnect.fr/api/v1/userinfo?schema=openid"
fcClientID := "211286433e39cce01db448d80181bdfd005554b19cd51b3fe7943f6b3b86ab6e"
fcClientSecret := "2791a731e6a59f56b6b4dd0d08c9b1f593b5f3658b9fd731cb24248e2669af4b"
// TODO : Check the state
fcCode := r.URL.Query().Get("code")
data := url.Values{}
data.Set("grant_type", "authorization_code")
data.Set("redirect_uri", "http://localhost:8080/callback")
data.Set("client_id", fcClientID)
data.Set("client_secret", fcClientSecret)
data.Set("code", fcCode)
client := &http.Client{}
req, err := http.NewRequest(http.MethodPost, fcToken, strings.NewReader(data.Encode()))
if err != nil {
return Identity{}, fmt.Errorf("could not perform the request to France Connect token url")
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
resp, err := client.Do(req)
if err != nil {
return Identity{}, fmt.Errorf("could not perform the request to France Connect token url")
}
// Get the access_token information
rBody, _ := ioutil.ReadAll(resp.Body)
fmt.Printf("rBody: %v\n", string(rBody))
accessToken := regexp.MustCompile(`.*"access_token":"([^"]*)`).FindStringSubmatch(string(rBody))[1]
// Call the user info endpoint
req, err = http.NewRequest(http.MethodGet, fcUserInfo, nil)
if err != nil {
return Identity{}, fmt.Errorf("could not perform the request to France Connect userinfo url")
}
req.Header.Add("Authorization", "Bearer "+accessToken)
resp, err = client.Do(req)
if err != nil {
return Identity{}, fmt.Errorf("could not perform the request to France Connect userinfo url")
}
rBodyFC, _ := ioutil.ReadAll(resp.Body)
i := Identity{}
err = json.Unmarshal(rBodyFC, &i)
if err != nil {
return Identity{}, fmt.Errorf("unmarshall the France Connect user info")
}
return i, nil
}
......@@ -7,8 +7,12 @@
l'entreprise ou de l'établissement portant le numéro SIREN/SIRET
{{.Sirent}}.
</p>
<p>Pour valider cette demande, veuillez cliquer sur ce lien :</p>
<a href="localhost:8080/api/matcher/validate&code={{.Token}}">Valider</a>
<p>
Pour valider cette demande, veuillez cliquer sur ce lien :
<a href="http://localhost:8080/api/matcher/validate?code={{.Token}}"
>Valider.</a
>
</p>
<p>
Pour refuser cette demande, il suffit de ne rien faire et de supprimer ce
mail.
......
......@@ -10,7 +10,10 @@ import (
"regexp"
"time"
"forge.grandlyon.com/npernoud/glcpro/internal/mandate"
"forge.grandlyon.com/npernoud/glcpro/pkg/apientreprise"
"forge.grandlyon.com/npernoud/glcpro/pkg/common"
"forge.grandlyon.com/npernoud/glcpro/pkg/franceconnect"
"forge.grandlyon.com/npernoud/glcpro/pkg/oidcserver"
"forge.grandlyon.com/npernoud/glcpro/pkg/tokens"
)
......@@ -68,16 +71,61 @@ func CreateMatcherServer() *http.ServeMux {
mux.HandleFunc("/validate", func(w http.ResponseWriter, r *http.Request) {
// Unpack the token to work out who the mandate is for
md := MandateDemand{}
tokens.ExtractAndValidateToken(r, "", &md, false)
// Perform an France Connect authentication
// Store the demand in a cookie
tokens.CreateCookie(md, "localhost", "MandateDemand", 60*time.Second, w)
// Check that the France Connect user can create a mandate : he is the CEO of the company or has a transitive mandate
// Set a cookie to redirect to the needed callback route from the front end generic callback
cookie := http.Cookie{Name: "callbackRoute", Domain: "localhost", Path: "/", Value: "/api/matcher/callback", MaxAge: 60, Secure: false, HttpOnly: false, SameSite: http.SameSiteLaxMode}
http.SetCookie(w, &cookie)
// Perform an France Connect authentication ... and wait for the callback
franceconnect.RedirectToFranceConnect(w, r)
})
mux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
// Get the mandater identity from
i, err := franceconnect.HandleCallBack(w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// Get the redirect url from the cookie
md := MandateDemand{}
_, err = tokens.ExtractAndValidateToken(r, "MandateDemand", &md, false)
if err != nil {
http.Error(w, "could not get the mandate demand data from cookie", http.StatusInternalServerError)
return
}
// Create the mandate
// Check that the France Connect user can create a mandate : he is the CEO of the company or has a transitive mandate
// Get the data from the API Entreprise
e, _, err := apientreprise.GetData(md.Sirent)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// TODO : Inform the original asket that is demand has been validated/rejected
// TODO : Iterate on all MandatairesSociaux and clean the big if, refactor with oidcserver
if len(e.MandatairesSociaux) < 1 {
http.Error(w, "there is no Mandataires Sociaux for this SIREN/T", http.StatusOK)
return
}
if e.MandatairesSociaux[0].Nom == i.FamilyName || e.MandatairesSociaux[0].Prenom == i.GivenName || e.MandatairesSociaux[0].DateNaissance == i.Birthdate {
// Create the mandate
mandate.Create(md.Sirent, md.AskerSub, i.Sub)
w.Write([]byte("the mandate was successfully created "))
return
}
w.Write([]byte("the mandate could not be created, you do not seem to be the company CEO"))
return
// TODO : Inform the original asker that is demand has been validated/rejected
})
return mux
}
......
......@@ -3,17 +3,15 @@ package oidcserver
import (
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"time"
"forge.grandlyon.com/npernoud/glcpro/internal/mandate"
"forge.grandlyon.com/npernoud/glcpro/pkg/apientreprise"
"forge.grandlyon.com/npernoud/glcpro/pkg/common"
"forge.grandlyon.com/npernoud/glcpro/pkg/franceconnect"
"forge.grandlyon.com/npernoud/glcpro/pkg/tokens"
)
......@@ -21,25 +19,14 @@ type ClientRequestData struct {
RedirectURI string
State string
Sirent string
Id FCIdentity
Id franceconnect.Identity
}
type GLCId struct {
Id FCIdentity `json:"id_token"`
Id franceconnect.Identity `json:"id_token"`
E apientreprise.Entreprise `json:"entreprise"`
}
type FCIdentity struct {
GivenName string `json:"given_name"`
FamilyName string `json:"family_name"`
Birthdate string `json:"birthdate"`
Gender string `json:"gender"`
Birthplace string `json:"birthplace"`
Birthcountry string `json:"birthcountry"`
PreferredUsername string `json:"preferred_username"`
Sub string `json:"sub"`
}
// CreateOIDCServer creates a Open ID Connect Server as proxy to France Connect
func CreateOIDCServer() *http.ServeMux {
rd := ClientRequestData{}
......@@ -79,80 +66,28 @@ func CreateOIDCServer() *http.ServeMux {
rd.Sirent = query.Get("sirent")
tokens.CreateCookie(rd, "localhost", "ClientRequestData", 60*time.Second, w)
// Set a cookie to redirect to the needed callback route from the front end generic callback
cookie := http.Cookie{Name: "callbackRoute", Domain: "localhost", Path: "/", Value: "/api/oidc/callback", MaxAge: 60, Secure: false, HttpOnly: false, SameSite: http.SameSiteLaxMode}
http.SetCookie(w, &cookie)
// Redirect to France Connect with the callback as parameter
// TODO : France Connect parameters as env variables
fcAuth := "https://fcp.integ01.dev-franceconnect.fr/api/v1/authorize"
fcClientID := "211286433e39cce01db448d80181bdfd005554b19cd51b3fe7943f6b3b86ab6e"
params := url.Values{}
// TODO : State and nonce
params.Add("state", "A_RANDOM_STATE")
params.Add("nonce", "A_RANDOM_NONCE")
params.Add("scope", "openid identite_pivot")
// TODO : Redirect from request
params.Add("redirect_uri", "http://localhost:8080"+"/callback")
params.Add("client_id", fcClientID)
params.Add("response_type", "code")
http.Redirect(w, r, fcAuth+"?"+params.Encode(), http.StatusFound)
franceconnect.RedirectToFranceConnect(w, r)
// And that's all the reste will be handed by France Connect callback : get the France Connect identity, do the API Entreprise call, get the original parameters from the client and redirect to the original client
})
// Handles the France Connect Callback, get the user info from the token, and end the original response
mux.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
// TODO : fcToken and client secret from env
fcToken := "https://fcp.integ01.dev-franceconnect.fr/api/v1/token"
fcUserInfo := "https://fcp.integ01.dev-franceconnect.fr/api/v1/userinfo?schema=openid"
fcClientID := "211286433e39cce01db448d80181bdfd005554b19cd51b3fe7943f6b3b86ab6e"
fcClientSecret := "2791a731e6a59f56b6b4dd0d08c9b1f593b5f3658b9fd731cb24248e2669af4b"
// TODO : Check the state
fcCode := r.URL.Query().Get("code")
data := url.Values{}
data.Set("grant_type", "authorization_code")
data.Set("redirect_uri", "http://localhost:8080/callback")
data.Set("client_id", fcClientID)
data.Set("client_secret", fcClientSecret)
data.Set("code", fcCode)
client := &http.Client{}
req, err := http.NewRequest(http.MethodPost, fcToken, strings.NewReader(data.Encode()))
if err != nil {
http.Error(w, "could not perform the request to France Connect token url", http.StatusInternalServerError)
return
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
resp, err := client.Do(req)
i, err := franceconnect.HandleCallBack(w, r)
if err != nil {
http.Error(w, "could not perform the request to France Connect token url", http.StatusInternalServerError)
return
}
// Get the access_token information
rBody, _ := ioutil.ReadAll(resp.Body)
fmt.Printf("rBody: %v\n", string(rBody))
accessToken := regexp.MustCompile(`.*"access_token":"([^"]*)`).FindStringSubmatch(string(rBody))[1]
// Call the user info endpoint
req, err = http.NewRequest(http.MethodGet, fcUserInfo, nil)
if err != nil {
http.Error(w, "could not perform the request to France Connect userinfo url", http.StatusInternalServerError)
return
}
req.Header.Add("Authorization", "Bearer "+accessToken)
resp, err = client.Do(req)
if err != nil {
http.Error(w, "could not perform the request to France Connect userinfo url", http.StatusInternalServerError)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
rBodyFC, _ := ioutil.ReadAll(resp.Body)
i := FCIdentity{}
json.Unmarshal(rBodyFC, &i)
// Get the redirect url from the cookie
rd := ClientRequestData{}
_, err = tokens.ExtractAndValidateToken(r, "ClientRequestData", &rd, false)
fmt.Printf("Request Data:%v\n", rd)
if err != nil {
http.Error(w, "could not get the initial client request data from cookie", http.StatusInternalServerError)
return
......@@ -165,19 +100,19 @@ func CreateOIDCServer() *http.ServeMux {
return
}
// Match the fetched company to the user company
// TODO : Check if there is a mandate
// TODO : Iterate on all MandatairesSociaux and clean the big if
if len(e.MandatairesSociaux) < 1 {
http.Error(w, "there is no Mandataires Sociaux for this SIREN/T", http.StatusOK)
return
}
if e.MandatairesSociaux[0].Nom != i.FamilyName || e.MandatairesSociaux[0].Prenom != i.GivenName || e.MandatairesSociaux[0].DateNaissance != i.Birthdate { // If not redirect to the matcher to ask for a mandate
// Add the asker identity to the request data
rd.Id = i
tokens.CreateCookie(rd, "localhost", "ClientRequestData", 60*time.Second, w)
http.Redirect(w, req, "/matcher", http.StatusFound)
return
if !mandate.Exists(rd.Sirent, i.Sub) { // If there is a mandate, continue
if len(e.MandatairesSociaux) < 1 {
http.Error(w, "there is no Mandataires Sociaux for this SIREN/T", http.StatusOK)
return
}
// TODO : Iterate on all MandatairesSociaux and clean the big if
if e.MandatairesSociaux[0].Nom != i.FamilyName || e.MandatairesSociaux[0].Prenom != i.GivenName || e.MandatairesSociaux[0].DateNaissance != i.Birthdate { // If not redirect to the matcher to ask for a mandate
// Add the asker identity to the request data
rd.Id = i
tokens.CreateCookie(rd, "localhost", "ClientRequestData", 60*time.Second, w)
http.Redirect(w, r, "/matcher", http.StatusFound)
return
}
}
// Redirect to the initial caller with an code that it actually the data bundled in an opaque token
......@@ -190,7 +125,7 @@ func CreateOIDCServer() *http.ServeMux {
http.Error(w, "could not create an authorisation code", http.StatusInternalServerError)
return
}
http.Redirect(w, req, rd.RedirectURI+"?code="+url.QueryEscape(code), http.StatusFound)
http.Redirect(w, r, rd.RedirectURI+"?code="+url.QueryEscape(code), http.StatusFound)
})
// Returns access token back to the user
......
......@@ -15,6 +15,7 @@ class Auth {
<p>Pour essayer avec une autre entreprise, prendre par exemple la Fnac : 350127460</p>
<input type="text" id="auth-sirent" value="000000001"></input>
<button id="auth-do">OK</button>
<Pour tester un mandatement, utiliser la valeur "000000001", mais avec une connexion France Connect avec le compte "sans_nom_dusage".</p>
`;
// TODO : Check SIREN/T is in input and looks like a SIREN/T
document.getElementById(`auth-do`).addEventListener("click", () => {
......
......@@ -17,7 +17,7 @@ class Matcher {
<p>
Voulez vous qu'une demande de mandatement soit envoyée au dirigeant ?
</p>
<input type="text" id="matcher-email" value="npernoud@grandlyon.com"></input>
<input type="text" id="matcher-email" value="nicolas@alpha.grandlyon.com"></input>
<button id="matcher-do">OK</button>
<button id="matcher-cancel">Annuler</button>
`;
......
......@@ -22,8 +22,18 @@ async function navigate() {
});
break;
case "/callback":
//Redirect to server callback
location.pathname = "/api/oidc/callback";
// Redirect to server callback according to cookie
try {
const callbackRoute = document.cookie
.split("; ")
.find((row) => row.startsWith("callbackRoute="))
.split("=")[1];
if (callbackRoute != "" && callbackRoute != null) {
location.pathname = callbackRoute;
}
} catch (e) {
console.error("no callback route was gotten from the cookie");
}
break;
default:
location.pathname = "auth";
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment