package server import ( "context" "log" "time" "git.autistici.org/ai3/go-common/clientutil" "git.autistici.org/id/usermetadb/client" "git.autistici.org/id/auth" ) // Inject an interface for testing purposes. type checkDeviceClient interface { CheckDevice(context.Context, string, string, *auth.DeviceInfo) (bool, error) } type deviceFilter struct { client checkDeviceClient } // The timeout for this RPC is very short, as it needs to be performed // synchronously with the authentication request. var deviceCheckTimeout = 3 * time.Second func newDeviceFilter(config *clientutil.BackendConfig) (*deviceFilter, error) { c, err := client.New(config) if err != nil { return nil, err } return &deviceFilter{c}, nil } func (f *deviceFilter) Filter(user *User, req *auth.Request, resp *auth.Response) *auth.Response { // If there is no DeviceInfo, skip. if req.DeviceInfo == nil { return resp } // If the status is != OK, skip. if resp.Status != auth.StatusOK { return resp } // Check if the device is known already, in which case we're // OK and don't need to do anything else. ctx, cancel := context.WithTimeout(context.Background(), deviceCheckTimeout) defer cancel() seen, err := f.client.CheckDevice(ctx, user.Shard, user.Name, req.DeviceInfo) if err != nil { log.Printf("usermetadb.CheckDevice error for %s: %v", user.Name, err) return resp } if !seen { // New device! Send out a warning and store it for the future. // Errors are logged but non-fatal. if err := f.sendNewDeviceEmail(user, req.DeviceInfo); err != nil { log.Printf("error sending new device email to %s: %v", user.Name, err) } } return resp } func (f *deviceFilter) sendNewDeviceEmail(user *User, dev *auth.DeviceInfo) error { // TODO: Not implemented. log.Printf("new device for user %s: %+v", user.Name, dev) return nil }