Merge: * don't show "sign out" button if authorization is disabled
Close #1093 * commit '49e535336be47cccee07f2e1b05f0d514aa91aa7': * changelog + client: get profile info * minor + GET /control/profile
This commit is contained in:
commit
03c4793010
|
@ -54,6 +54,7 @@ Contents:
|
||||||
* Log-in page
|
* Log-in page
|
||||||
* API: Log in
|
* API: Log in
|
||||||
* API: Log out
|
* API: Log out
|
||||||
|
* API: Get current user info
|
||||||
|
|
||||||
|
|
||||||
## Relations between subsystems
|
## Relations between subsystems
|
||||||
|
@ -1207,7 +1208,7 @@ YAML configuration:
|
||||||
|
|
||||||
Session DB file:
|
Session DB file:
|
||||||
|
|
||||||
session="..." expire=123456
|
session="..." user=name expire=123456
|
||||||
...
|
...
|
||||||
|
|
||||||
Session data is SHA(random()+name+password).
|
Session data is SHA(random()+name+password).
|
||||||
|
@ -1270,3 +1271,20 @@ Response:
|
||||||
302 Found
|
302 Found
|
||||||
Location: /login.html
|
Location: /login.html
|
||||||
Set-Cookie: session=...; Expires=Thu, 01 Jan 1970 00:00:00 GMT
|
Set-Cookie: session=...; Expires=Thu, 01 Jan 1970 00:00:00 GMT
|
||||||
|
|
||||||
|
|
||||||
|
### API: Get current user info
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
GET /control/profile
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
200 OK
|
||||||
|
|
||||||
|
{
|
||||||
|
"name":"..."
|
||||||
|
}
|
||||||
|
|
||||||
|
If no client is configured then authentication is disabled and server sends an empty response.
|
||||||
|
|
|
@ -213,6 +213,21 @@ export const getClients = () => async (dispatch) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getProfileRequest = createAction('GET_PROFILE_REQUEST');
|
||||||
|
export const getProfileFailure = createAction('GET_PROFILE_FAILURE');
|
||||||
|
export const getProfileSuccess = createAction('GET_PROFILE_SUCCESS');
|
||||||
|
|
||||||
|
export const getProfile = () => async (dispatch) => {
|
||||||
|
dispatch(getProfileRequest());
|
||||||
|
try {
|
||||||
|
const profile = await apiClient.getProfile();
|
||||||
|
dispatch(getProfileSuccess(profile));
|
||||||
|
} catch (error) {
|
||||||
|
dispatch(addErrorToast({ error }));
|
||||||
|
dispatch(getProfileFailure());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const dnsStatusRequest = createAction('DNS_STATUS_REQUEST');
|
export const dnsStatusRequest = createAction('DNS_STATUS_REQUEST');
|
||||||
export const dnsStatusFailure = createAction('DNS_STATUS_FAILURE');
|
export const dnsStatusFailure = createAction('DNS_STATUS_FAILURE');
|
||||||
export const dnsStatusSuccess = createAction('DNS_STATUS_SUCCESS');
|
export const dnsStatusSuccess = createAction('DNS_STATUS_SUCCESS');
|
||||||
|
@ -224,6 +239,7 @@ export const getDnsStatus = () => async (dispatch) => {
|
||||||
dispatch(dnsStatusSuccess(dnsStatus));
|
dispatch(dnsStatusSuccess(dnsStatus));
|
||||||
dispatch(getVersion());
|
dispatch(getVersion());
|
||||||
dispatch(getTlsStatus());
|
dispatch(getTlsStatus());
|
||||||
|
dispatch(getProfile());
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
dispatch(addErrorToast({ error }));
|
dispatch(addErrorToast({ error }));
|
||||||
dispatch(dnsStatusFailure());
|
dispatch(dnsStatusFailure());
|
||||||
|
|
|
@ -525,6 +525,14 @@ class Api {
|
||||||
};
|
};
|
||||||
return this.makeRequest(path, method, config);
|
return this.makeRequest(path, method, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Profile
|
||||||
|
GET_PROFILE = { path: 'profile', method: 'GET' };
|
||||||
|
|
||||||
|
getProfile() {
|
||||||
|
const { path, method } = this.GET_PROFILE;
|
||||||
|
return this.makeRequest(path, method);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiClient = new Api();
|
const apiClient = new Api();
|
||||||
|
|
|
@ -60,9 +60,11 @@ class Header extends Component {
|
||||||
/>
|
/>
|
||||||
<div className="header__column">
|
<div className="header__column">
|
||||||
<div className="header__right">
|
<div className="header__right">
|
||||||
<a href="/control/logout" className="btn btn-sm btn-outline-secondary">
|
{!dashboard.processingProfile && dashboard.name &&
|
||||||
<Trans>sign_out</Trans>
|
<a href="/control/logout" className="btn btn-sm btn-outline-secondary">
|
||||||
</a>
|
<Trans>sign_out</Trans>
|
||||||
|
</a>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -189,6 +189,14 @@ const dashboard = handleActions(
|
||||||
processingDnsSettings: false,
|
processingDnsSettings: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[actions.getProfileRequest]: state => ({ ...state, processingProfile: true }),
|
||||||
|
[actions.getProfileFailure]: state => ({ ...state, processingProfile: false }),
|
||||||
|
[actions.getProfileSuccess]: (state, { payload }) => ({
|
||||||
|
...state,
|
||||||
|
name: payload.name,
|
||||||
|
processingProfile: false,
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
processing: true,
|
processing: true,
|
||||||
|
@ -198,6 +206,7 @@ const dashboard = handleActions(
|
||||||
processingClients: true,
|
processingClients: true,
|
||||||
processingUpdate: false,
|
processingUpdate: false,
|
||||||
processingDnsSettings: true,
|
processingDnsSettings: true,
|
||||||
|
processingProfile: true,
|
||||||
upstreamDns: '',
|
upstreamDns: '',
|
||||||
bootstrapDns: '',
|
bootstrapDns: '',
|
||||||
allServers: false,
|
allServers: false,
|
||||||
|
@ -209,6 +218,7 @@ const dashboard = handleActions(
|
||||||
dnsVersion: '',
|
dnsVersion: '',
|
||||||
clients: [],
|
clients: [],
|
||||||
autoClients: [],
|
autoClients: [],
|
||||||
|
name: '',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
118
home/auth.go
118
home/auth.go
|
@ -20,10 +20,44 @@ import (
|
||||||
const cookieTTL = 365 * 24 // in hours
|
const cookieTTL = 365 * 24 // in hours
|
||||||
const expireTime = 30 * 24 // in hours
|
const expireTime = 30 * 24 // in hours
|
||||||
|
|
||||||
|
type session struct {
|
||||||
|
userName string
|
||||||
|
expire uint32 // expiration time (in seconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
expire byte[4]
|
||||||
|
name_len byte[2]
|
||||||
|
name byte[]
|
||||||
|
*/
|
||||||
|
func (s *session) serialize() []byte {
|
||||||
|
var data []byte
|
||||||
|
data = make([]byte, 4+2+len(s.userName))
|
||||||
|
binary.BigEndian.PutUint32(data[0:4], s.expire)
|
||||||
|
binary.BigEndian.PutUint16(data[4:6], uint16(len(s.userName)))
|
||||||
|
copy(data[6:], []byte(s.userName))
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) deserialize(data []byte) bool {
|
||||||
|
if len(data) < 4+2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s.expire = binary.BigEndian.Uint32(data[0:4])
|
||||||
|
nameLen := binary.BigEndian.Uint16(data[4:6])
|
||||||
|
data = data[6:]
|
||||||
|
|
||||||
|
if len(data) < int(nameLen) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
s.userName = string(data)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// Auth - global object
|
// Auth - global object
|
||||||
type Auth struct {
|
type Auth struct {
|
||||||
db *bbolt.DB
|
db *bbolt.DB
|
||||||
sessions map[string]uint32 // session -> expiration time (in seconds)
|
sessions map[string]*session // session name -> session data
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
users []User
|
users []User
|
||||||
}
|
}
|
||||||
|
@ -37,7 +71,7 @@ type User struct {
|
||||||
// InitAuth - create a global object
|
// InitAuth - create a global object
|
||||||
func InitAuth(dbFilename string, users []User) *Auth {
|
func InitAuth(dbFilename string, users []User) *Auth {
|
||||||
a := Auth{}
|
a := Auth{}
|
||||||
a.sessions = make(map[string]uint32)
|
a.sessions = make(map[string]*session)
|
||||||
rand.Seed(time.Now().UTC().Unix())
|
rand.Seed(time.Now().UTC().Unix())
|
||||||
var err error
|
var err error
|
||||||
a.db, err = bbolt.Open(dbFilename, 0644, nil)
|
a.db, err = bbolt.Open(dbFilename, 0644, nil)
|
||||||
|
@ -56,6 +90,10 @@ func (a *Auth) Close() {
|
||||||
_ = a.db.Close()
|
_ = a.db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func bucketName() []byte {
|
||||||
|
return []byte("sessions-2")
|
||||||
|
}
|
||||||
|
|
||||||
// load sessions from file, remove expired sessions
|
// load sessions from file, remove expired sessions
|
||||||
func (a *Auth) loadSessions() {
|
func (a *Auth) loadSessions() {
|
||||||
tx, err := a.db.Begin(true)
|
tx, err := a.db.Begin(true)
|
||||||
|
@ -67,16 +105,22 @@ func (a *Auth) loadSessions() {
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
bkt := tx.Bucket([]byte("sessions"))
|
bkt := tx.Bucket(bucketName())
|
||||||
if bkt == nil {
|
if bkt == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
removed := 0
|
removed := 0
|
||||||
|
|
||||||
|
if tx.Bucket([]byte("sessions")) != nil {
|
||||||
|
_ = tx.DeleteBucket([]byte("sessions"))
|
||||||
|
removed = 1
|
||||||
|
}
|
||||||
|
|
||||||
now := uint32(time.Now().UTC().Unix())
|
now := uint32(time.Now().UTC().Unix())
|
||||||
forEach := func(k, v []byte) error {
|
forEach := func(k, v []byte) error {
|
||||||
i := binary.BigEndian.Uint32(v)
|
s := session{}
|
||||||
if i <= now {
|
if !s.deserialize(v) || s.expire <= now {
|
||||||
err = bkt.Delete(k)
|
err = bkt.Delete(k)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Auth: bbolt.Delete: %s", err)
|
log.Error("Auth: bbolt.Delete: %s", err)
|
||||||
|
@ -85,7 +129,8 @@ func (a *Auth) loadSessions() {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
a.sessions[hex.EncodeToString(k)] = i
|
|
||||||
|
a.sessions[hex.EncodeToString(k)] = &s
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
_ = bkt.ForEach(forEach)
|
_ = bkt.ForEach(forEach)
|
||||||
|
@ -99,11 +144,15 @@ func (a *Auth) loadSessions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// store session data in file
|
// store session data in file
|
||||||
func (a *Auth) storeSession(data []byte, expire uint32) {
|
func (a *Auth) addSession(data []byte, s *session) {
|
||||||
a.lock.Lock()
|
a.lock.Lock()
|
||||||
a.sessions[hex.EncodeToString(data)] = expire
|
a.sessions[hex.EncodeToString(data)] = s
|
||||||
a.lock.Unlock()
|
a.lock.Unlock()
|
||||||
|
a.storeSession(data, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// store session data in file
|
||||||
|
func (a *Auth) storeSession(data []byte, s *session) {
|
||||||
tx, err := a.db.Begin(true)
|
tx, err := a.db.Begin(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Auth: bbolt.Begin: %s", err)
|
log.Error("Auth: bbolt.Begin: %s", err)
|
||||||
|
@ -113,15 +162,12 @@ func (a *Auth) storeSession(data []byte, expire uint32) {
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
bkt, err := tx.CreateBucketIfNotExists([]byte("sessions"))
|
bkt, err := tx.CreateBucketIfNotExists(bucketName())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Auth: bbolt.CreateBucketIfNotExists: %s", err)
|
log.Error("Auth: bbolt.CreateBucketIfNotExists: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var val []byte
|
err = bkt.Put(data, s.serialize())
|
||||||
val = make([]byte, 4)
|
|
||||||
binary.BigEndian.PutUint32(val, expire)
|
|
||||||
err = bkt.Put(data, val)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Auth: bbolt.Put: %s", err)
|
log.Error("Auth: bbolt.Put: %s", err)
|
||||||
return
|
return
|
||||||
|
@ -147,7 +193,7 @@ func (a *Auth) removeSession(sess []byte) {
|
||||||
_ = tx.Rollback()
|
_ = tx.Rollback()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
bkt := tx.Bucket([]byte("sessions"))
|
bkt := tx.Bucket(bucketName())
|
||||||
if bkt == nil {
|
if bkt == nil {
|
||||||
log.Error("Auth: bbolt.Bucket")
|
log.Error("Auth: bbolt.Bucket")
|
||||||
return
|
return
|
||||||
|
@ -174,12 +220,12 @@ func (a *Auth) CheckSession(sess string) int {
|
||||||
update := false
|
update := false
|
||||||
|
|
||||||
a.lock.Lock()
|
a.lock.Lock()
|
||||||
expire, ok := a.sessions[sess]
|
s, ok := a.sessions[sess]
|
||||||
if !ok {
|
if !ok {
|
||||||
a.lock.Unlock()
|
a.lock.Unlock()
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
if expire <= now {
|
if s.expire <= now {
|
||||||
delete(a.sessions, sess)
|
delete(a.sessions, sess)
|
||||||
key, _ := hex.DecodeString(sess)
|
key, _ := hex.DecodeString(sess)
|
||||||
a.removeSession(key)
|
a.removeSession(key)
|
||||||
|
@ -188,17 +234,17 @@ func (a *Auth) CheckSession(sess string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
newExpire := now + expireTime*60*60
|
newExpire := now + expireTime*60*60
|
||||||
if expire/(24*60*60) != newExpire/(24*60*60) {
|
if s.expire/(24*60*60) != newExpire/(24*60*60) {
|
||||||
// update expiration time once a day
|
// update expiration time once a day
|
||||||
update = true
|
update = true
|
||||||
a.sessions[sess] = newExpire
|
s.expire = newExpire
|
||||||
}
|
}
|
||||||
|
|
||||||
a.lock.Unlock()
|
a.lock.Unlock()
|
||||||
|
|
||||||
if update {
|
if update {
|
||||||
key, _ := hex.DecodeString(sess)
|
key, _ := hex.DecodeString(sess)
|
||||||
a.storeSession(key, expire)
|
a.storeSession(key, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
@ -238,8 +284,10 @@ func httpCookie(req loginJSON) string {
|
||||||
expstr = expstr[:len(expstr)-len("UTC")] // "UTC" -> "GMT"
|
expstr = expstr[:len(expstr)-len("UTC")] // "UTC" -> "GMT"
|
||||||
expstr += "GMT"
|
expstr += "GMT"
|
||||||
|
|
||||||
expireSess := uint32(now.Unix()) + expireTime*60*60
|
s := session{}
|
||||||
config.auth.storeSession(sess, expireSess)
|
s.userName = u.Name
|
||||||
|
s.expire = uint32(now.Unix()) + expireTime*60*60
|
||||||
|
config.auth.addSession(sess, &s)
|
||||||
|
|
||||||
return fmt.Sprintf("session=%s; Path=/; HttpOnly; Expires=%s", hex.EncodeToString(sess), expstr)
|
return fmt.Sprintf("session=%s; Path=/; HttpOnly; Expires=%s", hex.EncodeToString(sess), expstr)
|
||||||
}
|
}
|
||||||
|
@ -402,6 +450,34 @@ func (a *Auth) UserFind(login string, password string) User {
|
||||||
return User{}
|
return User{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCurrentUser - get the current user
|
||||||
|
func (a *Auth) GetCurrentUser(r *http.Request) User {
|
||||||
|
cookie, err := r.Cookie("session")
|
||||||
|
if err != nil {
|
||||||
|
// there's no Cookie, check Basic authentication
|
||||||
|
user, pass, ok := r.BasicAuth()
|
||||||
|
if ok {
|
||||||
|
u := config.auth.UserFind(user, pass)
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.lock.Lock()
|
||||||
|
s, ok := a.sessions[cookie.Value]
|
||||||
|
if !ok {
|
||||||
|
a.lock.Unlock()
|
||||||
|
return User{}
|
||||||
|
}
|
||||||
|
for _, u := range a.users {
|
||||||
|
if u.Name == s.userName {
|
||||||
|
a.lock.Unlock()
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a.lock.Unlock()
|
||||||
|
return User{}
|
||||||
|
}
|
||||||
|
|
||||||
// GetUsers - get users
|
// GetUsers - get users
|
||||||
func (a *Auth) GetUsers() []User {
|
func (a *Auth) GetUsers() []User {
|
||||||
a.lock.Lock()
|
a.lock.Lock()
|
||||||
|
|
|
@ -28,6 +28,7 @@ func TestAuth(t *testing.T) {
|
||||||
User{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"},
|
User{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"},
|
||||||
}
|
}
|
||||||
a := InitAuth(fn, nil)
|
a := InitAuth(fn, nil)
|
||||||
|
s := session{}
|
||||||
|
|
||||||
user := User{Name: "name"}
|
user := User{Name: "name"}
|
||||||
a.UserAdd(&user, "password")
|
a.UserAdd(&user, "password")
|
||||||
|
@ -38,12 +39,16 @@ func TestAuth(t *testing.T) {
|
||||||
sess := getSession(&users[0])
|
sess := getSession(&users[0])
|
||||||
sessStr := hex.EncodeToString(sess)
|
sessStr := hex.EncodeToString(sess)
|
||||||
|
|
||||||
|
now := time.Now().UTC().Unix()
|
||||||
// check expiration
|
// check expiration
|
||||||
a.storeSession(sess, uint32(time.Now().UTC().Unix()))
|
s.expire = uint32(now)
|
||||||
|
a.addSession(sess, &s)
|
||||||
assert.True(t, a.CheckSession(sessStr) == 1)
|
assert.True(t, a.CheckSession(sessStr) == 1)
|
||||||
|
|
||||||
// add session with TTL = 2 sec
|
// add session with TTL = 2 sec
|
||||||
a.storeSession(sess, uint32(time.Now().UTC().Unix()+2))
|
s = session{}
|
||||||
|
s.expire = uint32(now + 2)
|
||||||
|
a.addSession(sess, &s)
|
||||||
assert.True(t, a.CheckSession(sessStr) == 0)
|
assert.True(t, a.CheckSession(sessStr) == 0)
|
||||||
|
|
||||||
a.Close()
|
a.Close()
|
||||||
|
@ -53,6 +58,9 @@ func TestAuth(t *testing.T) {
|
||||||
|
|
||||||
// the session is still alive
|
// the session is still alive
|
||||||
assert.True(t, a.CheckSession(sessStr) == 0)
|
assert.True(t, a.CheckSession(sessStr) == 0)
|
||||||
|
// reset our expiration time because CheckSession() has just updated it
|
||||||
|
s.expire = uint32(now + 2)
|
||||||
|
a.storeSession(sess, &s)
|
||||||
a.Close()
|
a.Close()
|
||||||
|
|
||||||
u := a.UserFind("name", "password")
|
u := a.UserFind("name", "password")
|
||||||
|
|
|
@ -377,6 +377,23 @@ func checkDNS(input string, bootstrap []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type profileJSON struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleGetProfile(w http.ResponseWriter, r *http.Request) {
|
||||||
|
pj := profileJSON{}
|
||||||
|
u := config.auth.GetCurrentUser(r)
|
||||||
|
pj.Name = u.Name
|
||||||
|
|
||||||
|
data, err := json.Marshal(pj)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, http.StatusInternalServerError, "json.Marshal: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, _ = w.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
// --------------
|
// --------------
|
||||||
// DNS-over-HTTPS
|
// DNS-over-HTTPS
|
||||||
// --------------
|
// --------------
|
||||||
|
@ -416,6 +433,7 @@ func registerControlHandlers() {
|
||||||
|
|
||||||
httpRegister(http.MethodGet, "/control/access/list", handleAccessList)
|
httpRegister(http.MethodGet, "/control/access/list", handleAccessList)
|
||||||
httpRegister(http.MethodPost, "/control/access/set", handleAccessSet)
|
httpRegister(http.MethodPost, "/control/access/set", handleAccessSet)
|
||||||
|
httpRegister("GET", "/control/profile", handleGetProfile)
|
||||||
|
|
||||||
RegisterFilteringHandlers()
|
RegisterFilteringHandlers()
|
||||||
RegisterTLSHandlers()
|
RegisterTLSHandlers()
|
||||||
|
|
|
@ -1,6 +1,23 @@
|
||||||
# AdGuard Home API Change Log
|
# AdGuard Home API Change Log
|
||||||
|
|
||||||
|
|
||||||
|
## v0.99.1: API changes
|
||||||
|
|
||||||
|
### API: Get current user info: GET /control/profile
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
GET /control/profile
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
200 OK
|
||||||
|
|
||||||
|
{
|
||||||
|
"name":"..."
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
## v0.99: incompatible API changes
|
## v0.99: incompatible API changes
|
||||||
|
|
||||||
* A note about web user authentication
|
* A note about web user authentication
|
||||||
|
|
|
@ -970,6 +970,18 @@ paths:
|
||||||
302:
|
302:
|
||||||
description: OK
|
description: OK
|
||||||
|
|
||||||
|
/profile:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- global
|
||||||
|
operationId: getProfile
|
||||||
|
summary: ""
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/ProfileInfo"
|
||||||
|
|
||||||
definitions:
|
definitions:
|
||||||
ServerStatus:
|
ServerStatus:
|
||||||
type: "object"
|
type: "object"
|
||||||
|
@ -1559,6 +1571,14 @@ definitions:
|
||||||
description: "Network interfaces dictionary (key is the interface name)"
|
description: "Network interfaces dictionary (key is the interface name)"
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
$ref: "#/definitions/NetInterface"
|
$ref: "#/definitions/NetInterface"
|
||||||
|
|
||||||
|
ProfileInfo:
|
||||||
|
type: "object"
|
||||||
|
description: "Information about the current user"
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: "string"
|
||||||
|
|
||||||
Client:
|
Client:
|
||||||
type: "object"
|
type: "object"
|
||||||
description: "Client information"
|
description: "Client information"
|
||||||
|
|
|
@ -346,7 +346,7 @@ func (s *statsCtx) loadUnitFromDB(tx *bolt.Tx, id uint32) *unitDB {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Tracef("Loading unit %d", id)
|
// log.Tracef("Loading unit %d", id)
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
buf.Write(bkt.Get([]byte{0}))
|
buf.Write(bkt.Get([]byte{0}))
|
||||||
|
|
Loading…
Reference in New Issue