Commit 2cd29e57 authored by shammash's avatar shammash
Browse files

Update id/usermetadb

Signed-off-by: shammash's avatarshammash <>
parent b0d2931a
......@@ -26,6 +26,11 @@ a specific device and an account if one is in possession of the
server-side log database (only partially mitigated by the fact that
the cookie is encrypted).
`usermetadb` also stores last-login information for internal infrastructure
maintenance. The idea is to retain the minimal amount of information to perform
tasks such as "disable accounts that have not been active for more than N
The server exports an API over HTTP/HTTPS, all requests should be made
......@@ -39,14 +44,14 @@ and the *analysis* API.
## Log API
`/api/add_log` (*AddLogRequest*)
Stores a new log entry for a user in the database. The request must be
a `LogEntry` object. The method returns an empty response. If the log
entry contains device information, the list of devices for the
specified user is updated with that information.
`/api/get_user_logs` (*GetUserLogsRequest*) -> *GetUserLogsResponse*
Returns recent logs for a specific user.
`/api/get_user_devices` (*GetUserDevicesRequest*) -> *GetUserDevicesResponse*
......@@ -60,6 +65,20 @@ Returns the list of known devices for a user.
Returns information about a device, whether we have seen it before, if
the localization information matches the historical trend, etc.
## Last-login API
`/api/set_last_login` (*SetLastLoginRequest*)
Stores the last login of a user in the database. The request must be a
`LastLoginEntry` object. The method returns an empty response. The service name
must be specified in the last login entry.
`/api/get_last_login` (*GetLastLoginRequest*) -> *GetLastLoginResponse*
Returns the last login of a given user. If the service name is specified it
returns the last login for that specific service, otherwise return last login
for all services.
# Configuration
......@@ -15,6 +15,8 @@ type Client interface {
AddLog(context.Context, string, *usermetadb.LogEntry) error
GetUserDevices(context.Context, string, string) ([]*usermetadb.MetaDeviceInfo, error)
GetUserLogs(context.Context, string, string, int, int) ([]*usermetadb.LogEntry, error)
SetLastLogin(context.Context, string, *usermetadb.LastLoginEntry) error
GetLastLogin(context.Context, string, string, string) ([]*usermetadb.LastLoginEntry, error)
type udbClient struct {
......@@ -63,3 +65,19 @@ func (c *udbClient) GetUserLogs(ctx context.Context, shard, username string, max
err :=, shard, "/api/get_user_logs", &req, &resp)
return resp.Results, err
func (c *udbClient) SetLastLogin(ctx context.Context, shard string, entry *usermetadb.LastLoginEntry) error {
req := usermetadb.SetLastLoginRequest{LastLogin: entry}
return, shard, "/api/set_last_login", &req, nil)
func (c *udbClient) GetLastLogin(ctx context.Context, shard string, username string, service string) ([]*usermetadb.LastLoginEntry, error) {
req := usermetadb.GetLastLoginRequest{
Username: username,
Service: service,
var resp usermetadb.GetLastLoginResponse
err :=, shard, "/api/get_last_login", &req, &resp)
return resp.Results, err
......@@ -92,3 +92,35 @@ type GetUserLogsRequest struct {
type GetUserLogsResponse struct {
Results []*LogEntry `json:"result"`
type LastLoginEntry struct {
Timestamp time.Time `json:"timestamp"`
Username string `json:"username"`
Service string `json:"service"`
func (e *LastLoginEntry) Validate() error {
if e.Username == "" {
return errors.New("invalid last login entry: missing username")
if e.Service == "" {
return errors.New("invalid last login entry: missing service")
return nil
type SetLastLoginRequest struct {
LastLogin *LastLoginEntry `json:"last_login"`
type SetLastLoginResponse struct{}
type GetLastLoginRequest struct {
Username string `json:"username"`
Service string `json:"service,omitempty"`
type GetLastLoginResponse struct {
Results []*LastLoginEntry `json:"result"`
......@@ -39,16 +39,16 @@
"revisionTime": "2019-01-29T12:17:45Z"
"checksumSHA1": "NtTOmajRf6xh0mkVSm18L70ncl0=",
"checksumSHA1": "J0QeD9LVccFOejgPKa0td8JD0rY=",
"path": "",
"revision": "fa081ac5509ba4ed97ab6a447d611c1adcf9e764",
"revisionTime": "2018-11-03T07:16:06Z"
"revision": "61e5a7b24130b36be4964ffadae1b4a9ef803a7a",
"revisionTime": "2019-02-09T10:52:39Z"
"checksumSHA1": "kwosXxbzygo9ZUZSYvD+WiAbM3Q=",
"checksumSHA1": "HnFofi1vg8of8d1uVSRUX7LIAxI=",
"path": "",
"revision": "fa081ac5509ba4ed97ab6a447d611c1adcf9e764",
"revisionTime": "2018-11-03T07:16:06Z"
"revision": "61e5a7b24130b36be4964ffadae1b4a9ef803a7a",
"revisionTime": "2019-02-09T10:52:39Z"
"checksumSHA1": "yReqUM4tQkY+1YEI+L2d0SOzFWs=",
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment