Use secure cookies to store session identifier instead of localstorage
This commit is contained in:
parent
ad7dde0d0a
commit
5461823525
|
@ -34,7 +34,6 @@ package api
|
|||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
|
@ -138,14 +137,28 @@ func apiAuthHandler(f func(*config.Options, *happydns.User, httprouter.Params, i
|
|||
return
|
||||
}
|
||||
|
||||
if flds := strings.Fields(r.Header.Get("Authorization")); len(flds) != 2 || flds[0] != "Bearer" {
|
||||
var sessionid []byte
|
||||
|
||||
if cookie, err := r.Cookie("happydns_session"); err == nil {
|
||||
if sessionid, err = base64.StdEncoding.DecodeString(cookie.Value); err != nil {
|
||||
APIErrorResponse{
|
||||
err: fmt.Errorf("Unable to authenticate request due to invalid cookie value: %w", err),
|
||||
status: http.StatusUnauthorized,
|
||||
}.WriteResponse(w)
|
||||
return
|
||||
}
|
||||
} else if flds := strings.Fields(r.Header.Get("Authorization")); len(flds) == 2 && flds[0] == "Bearer" {
|
||||
if sessionid, err = base64.StdEncoding.DecodeString(flds[1]); err != nil {
|
||||
APIErrorResponse{
|
||||
err: fmt.Errorf("Unable to authenticate request due to invalid Authorization header value: %w", err),
|
||||
status: http.StatusUnauthorized,
|
||||
}.WriteResponse(w)
|
||||
}
|
||||
}
|
||||
|
||||
if sessionid == nil || len(sessionid) == 0 {
|
||||
APIErrorResponse{
|
||||
err: errors.New("Authorization required"),
|
||||
status: http.StatusUnauthorized,
|
||||
}.WriteResponse(w)
|
||||
} else if sessionid, err := base64.StdEncoding.DecodeString(flds[1]); err != nil {
|
||||
APIErrorResponse{
|
||||
err: err,
|
||||
err: fmt.Errorf("Authorization required"),
|
||||
status: http.StatusUnauthorized,
|
||||
}.WriteResponse(w)
|
||||
} else if session, err := storage.MainStore.GetSession(sessionid); err != nil {
|
||||
|
|
132
api/user_auth.go
132
api/user_auth.go
|
@ -32,9 +32,11 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
|
@ -48,10 +50,11 @@ import (
|
|||
var AuthFunc = checkAuth
|
||||
|
||||
func init() {
|
||||
router.GET("/api/users/auth", apiAuthHandler(validateAuthToken))
|
||||
router.POST("/api/users/auth", apiHandler(func(_ *config.Options, ps httprouter.Params, b io.Reader) Response {
|
||||
return AuthFunc(ps, b)
|
||||
router.GET("/api/users/auth", apiAuthHandler(displayAuthToken))
|
||||
router.POST("/api/users/auth", apiHandler(func(opts *config.Options, ps httprouter.Params, b io.Reader) Response {
|
||||
return AuthFunc(opts, ps, b)
|
||||
}))
|
||||
router.POST("/api/users/auth/logout", apiHandler(logout))
|
||||
}
|
||||
|
||||
type DisplayUser struct {
|
||||
|
@ -60,13 +63,78 @@ type DisplayUser struct {
|
|||
RegistrationTime *time.Time `json:"registration_time,omitempty"`
|
||||
}
|
||||
|
||||
func validateAuthToken(_ *config.Options, u *happydns.User, _ httprouter.Params, _ io.Reader) Response {
|
||||
func currentUser(u *happydns.User) *DisplayUser {
|
||||
return &DisplayUser{
|
||||
Id: u.Id,
|
||||
Email: u.Email,
|
||||
RegistrationTime: u.RegistrationTime,
|
||||
}
|
||||
}
|
||||
|
||||
func displayAuthToken(_ *config.Options, u *happydns.User, _ httprouter.Params, _ io.Reader) Response {
|
||||
return APIResponse{
|
||||
response: &DisplayUser{
|
||||
Id: u.Id,
|
||||
Email: u.Email,
|
||||
RegistrationTime: u.RegistrationTime,
|
||||
},
|
||||
response: currentUser(u),
|
||||
}
|
||||
}
|
||||
|
||||
func completeAuth(opts *config.Options, email string, service string) Response {
|
||||
var usr *happydns.User
|
||||
var err error
|
||||
|
||||
if !storage.MainStore.UserExists(email) {
|
||||
now := time.Now()
|
||||
usr = &happydns.User{
|
||||
Email: email,
|
||||
RegistrationTime: &now,
|
||||
}
|
||||
if err = storage.MainStore.CreateUser(usr); err != nil {
|
||||
return APIErrorResponse{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
} else if usr, err = storage.MainStore.GetUserByEmail(email); err != nil {
|
||||
return APIErrorResponse{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("New user logged as %q\n", usr.Email)
|
||||
|
||||
var session *happydns.Session
|
||||
if session, err = happydns.NewSession(usr); err != nil {
|
||||
return APIErrorResponse{
|
||||
err: err,
|
||||
}
|
||||
} else if err = storage.MainStore.CreateSession(session); err != nil {
|
||||
return APIErrorResponse{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return APIResponse{
|
||||
response: currentUser(usr),
|
||||
cookies: []*http.Cookie{&http.Cookie{
|
||||
Name: "happydns_session",
|
||||
Value: base64.StdEncoding.EncodeToString(session.Id),
|
||||
Path: opts.BaseURL + "/",
|
||||
Expires: time.Now().Add(30 * 24 * time.Hour),
|
||||
Secure: opts.DevProxy == "",
|
||||
HttpOnly: true,
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func logout(opts *config.Options, _ httprouter.Params, body io.Reader) Response {
|
||||
return APIResponse{
|
||||
response: true,
|
||||
cookies: []*http.Cookie{&http.Cookie{
|
||||
Name: "happydns_session",
|
||||
Value: "",
|
||||
Path: opts.BaseURL + "/",
|
||||
Expires: time.Unix(0, 0),
|
||||
Secure: opts.DevProxy == "",
|
||||
HttpOnly: true,
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,7 +143,7 @@ type loginForm struct {
|
|||
Password string
|
||||
}
|
||||
|
||||
func dummyAuth(_ httprouter.Params, body io.Reader) Response {
|
||||
func dummyAuth(opts *config.Options, _ httprouter.Params, body io.Reader) Response {
|
||||
var lf loginForm
|
||||
if err := json.NewDecoder(body).Decode(&lf); err != nil {
|
||||
return APIErrorResponse{
|
||||
|
@ -88,30 +156,11 @@ func dummyAuth(_ httprouter.Params, body io.Reader) Response {
|
|||
err: err,
|
||||
}
|
||||
} else {
|
||||
session, err := happydns.NewSession(user)
|
||||
if err != nil {
|
||||
return APIErrorResponse{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if err := storage.MainStore.CreateSession(session); err != nil {
|
||||
return APIErrorResponse{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
res := map[string]interface{}{}
|
||||
res["status"] = "OK"
|
||||
res["id_session"] = session.Id
|
||||
|
||||
return APIResponse{
|
||||
response: res,
|
||||
}
|
||||
return completeAuth(opts, user.Email, "dummy")
|
||||
}
|
||||
}
|
||||
|
||||
func checkAuth(_ httprouter.Params, body io.Reader) Response {
|
||||
func checkAuth(opts *config.Options, _ httprouter.Params, body io.Reader) Response {
|
||||
var lf loginForm
|
||||
if err := json.NewDecoder(body).Decode(&lf); err != nil {
|
||||
return APIErrorResponse{
|
||||
|
@ -129,25 +178,6 @@ func checkAuth(_ httprouter.Params, body io.Reader) Response {
|
|||
status: http.StatusUnauthorized,
|
||||
}
|
||||
} else {
|
||||
session, err := happydns.NewSession(user)
|
||||
if err != nil {
|
||||
return APIErrorResponse{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if err := storage.MainStore.CreateSession(session); err != nil {
|
||||
return APIErrorResponse{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
|
||||
res := map[string]interface{}{}
|
||||
res["status"] = "OK"
|
||||
res["id_session"] = session.Id
|
||||
|
||||
return APIResponse{
|
||||
response: res,
|
||||
}
|
||||
return completeAuth(opts, user.Email, "local")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,52 +97,66 @@
|
|||
<script>
|
||||
import axios from 'axios'
|
||||
|
||||
function updateSession (t) {
|
||||
if (sessionStorage.token !== undefined) {
|
||||
t.session = sessionStorage.token
|
||||
axios.defaults.headers.common.Authorization = 'Bearer '.concat(sessionStorage.token)
|
||||
axios.get('/api/users/auth')
|
||||
.then(
|
||||
(response) => {
|
||||
t.loggedUser = response.data
|
||||
},
|
||||
(error) => {
|
||||
t.$bvToast.toast(
|
||||
'Invalid session, your have been logged out: ' + error.response.data.errmsg + '. Please login again.', {
|
||||
title: 'Authentication timeout',
|
||||
autoHideDelay: 5000,
|
||||
variant: 'danger',
|
||||
toaster: 'b-toaster-content-right'
|
||||
}
|
||||
)
|
||||
t.session = null
|
||||
t.loggedUser = null
|
||||
delete sessionStorage.token
|
||||
t.$router.replace('/login')
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
|
||||
data: function () {
|
||||
return {
|
||||
loggedUser: null,
|
||||
session: null
|
||||
loggedUser: null
|
||||
}
|
||||
},
|
||||
|
||||
mounted () {
|
||||
updateSession(this)
|
||||
if (sessionStorage.loggedUser) {
|
||||
this.loggedUser = JSON.parse(sessionStorage.loggedUser)
|
||||
}
|
||||
this.updateSession()
|
||||
this.$on('login', this.login)
|
||||
},
|
||||
|
||||
methods: {
|
||||
logout () {
|
||||
sessionStorage.token = undefined
|
||||
updateSession(this)
|
||||
this.$router.push('/')
|
||||
axios
|
||||
.post('/api/users/auth/logout')
|
||||
.then(
|
||||
(response) => {
|
||||
delete sessionStorage.loggedUser
|
||||
this.loggedUser = null
|
||||
this.updateSession()
|
||||
this.$router.push('/')
|
||||
},
|
||||
(error) => {
|
||||
this.$bvToast.toast(
|
||||
'An error occurs when trying to logout: ' + error.response.data.errmsg, {
|
||||
title: 'Logout error',
|
||||
autoHideDelay: 5000,
|
||||
toaster: 'b-toaster-content-right'
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
updateSession () {
|
||||
axios.get('/api/users/auth')
|
||||
.then(
|
||||
(response) => {
|
||||
sessionStorage.loggedUser = JSON.stringify(response.data)
|
||||
this.loggedUser = response.data
|
||||
},
|
||||
(error) => {
|
||||
delete sessionStorage.loggedUser
|
||||
this.loggedUser = null
|
||||
this.$root.$bvToast.toast(
|
||||
'Invalid session, your have been logged out: ' + error.response.data.errmsg + '. Please login again.', {
|
||||
title: 'Authentication timeout',
|
||||
autoHideDelay: 5000,
|
||||
variant: 'danger',
|
||||
toaster: 'b-toaster-content-right'
|
||||
}
|
||||
)
|
||||
this.$router.replace('/login')
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
login (email, password) {
|
||||
|
@ -153,13 +167,13 @@ export default {
|
|||
})
|
||||
.then(
|
||||
(response) => {
|
||||
if (response.data.id_session) {
|
||||
sessionStorage.token = response.data.id_session
|
||||
}
|
||||
updateSession(this)
|
||||
sessionStorage.loggedUser = JSON.stringify(response.data)
|
||||
this.loggedUser = response.data
|
||||
this.$router.push('/')
|
||||
},
|
||||
(error) => {
|
||||
delete sessionStorage.loggedUser
|
||||
this.loggedUser = null
|
||||
this.$bvToast.toast(
|
||||
'An error occurs when trying to login: ' + error.response.data.errmsg, {
|
||||
title: 'Login error',
|
||||
|
|
|
@ -41,7 +41,7 @@ import ZoneList from '@/views/domain-list'
|
|||
export default {
|
||||
beforeRouteEnter (to, from, next) {
|
||||
next(vm => {
|
||||
if (sessionStorage.token === undefined) {
|
||||
if (sessionStorage.loggedUser === undefined) {
|
||||
if (to.path === '/') {
|
||||
var preferedLang = navigator.language.split('-')[0] || process.env.VUE_APP_I18N_LOCALE || 'en'
|
||||
if (preferedLang !== 'en' && preferedLang !== 'fr') {
|
||||
|
@ -61,7 +61,7 @@ export default {
|
|||
|
||||
data () {
|
||||
return {
|
||||
homeComponent: sessionStorage.token !== undefined ? ZoneList : ''
|
||||
homeComponent: sessionStorage.loggedUser !== undefined ? ZoneList : ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user