Pull request: 2470 session token
Merge in DNS/adguard-home from 2470-session-token to master Updates #2470. Squashed commit of the following: commit 02e874404808ee23000c49b4b2980b049dc4d0e6 Author: Eugene Burkov <e.burkov@adguard.com> Date: Mon Mar 1 20:11:35 2021 +0300 home: imp time formating commit 6f4a6c9b190b2672cecd3e3e31413b03d19f8771 Author: Eugene Burkov <e.burkov@adguard.com> Date: Mon Mar 1 18:48:15 2021 +0300 home: rm user's data from session token
This commit is contained in:
parent
91403d0b95
commit
94e783d572
@ -29,6 +29,12 @@ and this project adheres to
|
|||||||
[#2692]: https://github.com/AdguardTeam/AdGuardHome/issues/2692
|
[#2692]: https://github.com/AdguardTeam/AdGuardHome/issues/2692
|
||||||
[#2757]: https://github.com/AdguardTeam/AdGuardHome/issues/2757
|
[#2757]: https://github.com/AdguardTeam/AdGuardHome/issues/2757
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Session token doesn't contain user's information anymore ([#2470]).
|
||||||
|
|
||||||
|
[#2470]: https://github.com/AdguardTeam/AdGuardHome/issues/2470
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [v0.105.1] - 2021-02-15
|
## [v0.105.1] - 2021-02-15
|
||||||
|
@ -2,13 +2,10 @@ package home
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
|
||||||
"math/big"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -20,8 +17,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
cookieTTL = 365 * 24 // in hours
|
// cookieTTL is given in hours.
|
||||||
|
cookieTTL = 365 * 24
|
||||||
sessionCookieName = "agh_session"
|
sessionCookieName = "agh_session"
|
||||||
|
|
||||||
|
// sessionTokenSize is the length of session token in bytes.
|
||||||
|
sessionTokenSize = 16
|
||||||
)
|
)
|
||||||
|
|
||||||
type session struct {
|
type session struct {
|
||||||
@ -285,16 +286,29 @@ type loginJSON struct {
|
|||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSession(u *User) ([]byte, error) {
|
// newSessionToken returns cryptographically secure randomly generated slice of
|
||||||
maxSalt := big.NewInt(math.MaxUint32)
|
// bytes of sessionTokenSize length.
|
||||||
salt, err := rand.Int(rand.Reader, maxSalt)
|
//
|
||||||
|
// TODO(e.burkov): Think about using byte array instead of byte slice.
|
||||||
|
func newSessionToken() (data []byte, err error) {
|
||||||
|
randData := make([]byte, sessionTokenSize)
|
||||||
|
|
||||||
|
_, err = rand.Read(randData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
d := []byte(fmt.Sprintf("%s%s%s", salt, u.Name, u.PasswordHash))
|
return randData, nil
|
||||||
hash := sha256.Sum256(d)
|
}
|
||||||
return hash[:], nil
|
|
||||||
|
// cookieTimeFormat is the format to be used in (time.Time).Format for cookie's
|
||||||
|
// expiry field.
|
||||||
|
const cookieTimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
|
||||||
|
|
||||||
|
// cookieExpiryFormat returns the formatted exp to be used in cookie string.
|
||||||
|
// It's quite simple for now, but probably will be expanded in the future.
|
||||||
|
func cookieExpiryFormat(exp time.Time) (formatted string) {
|
||||||
|
return exp.Format(cookieTimeFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) httpCookie(req loginJSON) (string, error) {
|
func (a *Auth) httpCookie(req loginJSON) (string, error) {
|
||||||
@ -303,24 +317,23 @@ func (a *Auth) httpCookie(req loginJSON) (string, error) {
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sess, err := getSession(&u)
|
sess, err := newSessionToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
expire := now.Add(cookieTTL * time.Hour)
|
|
||||||
expstr := expire.Format(time.RFC1123)
|
|
||||||
expstr = expstr[:len(expstr)-len("UTC")] // "UTC" -> "GMT"
|
|
||||||
expstr += "GMT"
|
|
||||||
|
|
||||||
s := session{}
|
a.addSession(sess, &session{
|
||||||
s.userName = u.Name
|
userName: u.Name,
|
||||||
s.expire = uint32(now.Unix()) + a.sessionTTL
|
expire: uint32(now.Unix()) + a.sessionTTL,
|
||||||
a.addSession(sess, &s)
|
})
|
||||||
|
|
||||||
return fmt.Sprintf("%s=%s; Path=/; HttpOnly; Expires=%s",
|
return fmt.Sprintf(
|
||||||
sessionCookieName, hex.EncodeToString(sess), expstr), nil
|
"%s=%s; Path=/; HttpOnly; Expires=%s",
|
||||||
|
sessionCookieName, hex.EncodeToString(sess),
|
||||||
|
cookieExpiryFormat(now.Add(cookieTTL*time.Hour)),
|
||||||
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleLogin(w http.ResponseWriter, r *http.Request) {
|
func handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package home
|
package home
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -11,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
@ -24,14 +27,34 @@ func prepareTestDir() string {
|
|||||||
return dir
|
return dir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewSessionToken(t *testing.T) {
|
||||||
|
// Successful case.
|
||||||
|
token, err := newSessionToken()
|
||||||
|
require.Nil(t, err)
|
||||||
|
assert.Len(t, token, sessionTokenSize)
|
||||||
|
|
||||||
|
// Break the rand.Reader.
|
||||||
|
prevReader := rand.Reader
|
||||||
|
t.Cleanup(func() {
|
||||||
|
rand.Reader = prevReader
|
||||||
|
})
|
||||||
|
rand.Reader = &bytes.Buffer{}
|
||||||
|
|
||||||
|
// Unsuccessful case.
|
||||||
|
token, err = newSessionToken()
|
||||||
|
require.NotNil(t, err)
|
||||||
|
assert.Empty(t, token)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAuth(t *testing.T) {
|
func TestAuth(t *testing.T) {
|
||||||
dir := prepareTestDir()
|
dir := prepareTestDir()
|
||||||
defer func() { _ = os.RemoveAll(dir) }()
|
t.Cleanup(func() { _ = os.RemoveAll(dir) })
|
||||||
fn := filepath.Join(dir, "sessions.db")
|
fn := filepath.Join(dir, "sessions.db")
|
||||||
|
|
||||||
users := []User{
|
users := []User{{
|
||||||
{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"},
|
Name: "name",
|
||||||
}
|
PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2",
|
||||||
|
}}
|
||||||
a := InitAuth(fn, nil, 60)
|
a := InitAuth(fn, nil, 60)
|
||||||
s := session{}
|
s := session{}
|
||||||
|
|
||||||
@ -41,7 +64,7 @@ func TestAuth(t *testing.T) {
|
|||||||
assert.Equal(t, checkSessionNotFound, a.checkSession("notfound"))
|
assert.Equal(t, checkSessionNotFound, a.checkSession("notfound"))
|
||||||
a.RemoveSession("notfound")
|
a.RemoveSession("notfound")
|
||||||
|
|
||||||
sess, err := getSession(&users[0])
|
sess, err := newSessionToken()
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
sessStr := hex.EncodeToString(sess)
|
sessStr := hex.EncodeToString(sess)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user