package database import ( "context" "encoding/json" "fmt" "time" "github.com/go-redis/redis" "github.com/pkg/errors" "edge-infra.dev/pkg/edge/iam/barcode" "edge-infra.dev/pkg/edge/iam/config" "edge-infra.dev/pkg/edge/iam/crypto" iamErrors "edge-infra.dev/pkg/edge/iam/errors" ) type BarcodeUser struct { Key string `json:"key"` } func (s *Store) CreateBarcodeCode(_ context.Context, signature, subject, issuedBy, clientID, barcodeType, challenge string) (err error) { // as we are just issuing, setting 'used' field to false return s.createOrUpdateBarcodeCode(signature, subject, issuedBy, clientID, barcodeType, challenge, false) } func (s *Store) InvalidateBarcodeCode(_ context.Context, signature, subject, issuedBy, clientID, barcodeType, challenge string) (err error) { // client used this BarcodeCode, setting 'used' field to true return s.createOrUpdateBarcodeCode(signature, subject, issuedBy, clientID, barcodeType, challenge, true) } func (s *Store) CreateBarcodeKey(_ context.Context, challenge, barcodeKey string) (err error) { key := keyFrom(KeyPrefixBarcodeKey, challenge) // ToDo (must): update should not need all other fields except the one we want to update. payload := &barcode.BarcodeKey{ BarcodeKey: barcodeKey, } value, err := json.Marshal(payload) if err != nil { return errors.WithStack(err) } // used SET because, we update this key twice, when issued and used if err := s.RedisDB.Set(key, string(value), config.GetBarcodeCodeTTL()).Err(); err != nil { return errors.WithStack(err) } return nil } func (s *Store) GetBarcodeKey(_ context.Context, challenge string) (barcodeKey *barcode.BarcodeKey, err error) { key := keyFrom(KeyPrefixBarcodeKey, challenge) cmd := s.RedisDB.Get(key) if cmd.Err() == redis.Nil { return nil, errors.WithMessage(cmd.Err(), "barcode code not found") } data, _ := cmd.Bytes() err = json.Unmarshal(data, &barcodeKey) if err != nil { return nil, errors.WithMessage(err, "barcode schema not valid") } return barcodeKey, nil } func (s *Store) DeleteBarcodeKey(_ context.Context, challenge string) (err error) { key := keyFrom(KeyPrefixBarcodeKey, challenge) err = s.RedisDB.Del(key).Err() if err != nil { return errors.Wrap(err, fmt.Sprintf("Failed to delete key '%v'", key)) } return nil } // CreateBarcodeUser creates record with key: barcode-user:subject value: barcode key into DB. func (s *Store) CreateBarcodeUser(ctx context.Context, subject, barcodeKey string) (err error) { key := keyFrom(KeyPrefixBarcodeUser, subject) value, err := json.Marshal(&BarcodeUser{Key: barcodeKey}) if err != nil { return errors.WithStack(err) } if err := s.updateDoc(ctx, key, value, WithExpiration(config.GetBarcodeUserTTL())); err != nil { return errors.WithStack(err) } // 'Set' because this keep getting updated as and when user prints barcode // if err := s.RedisDB.Set(key, barcodeKey, config.GetBarcodeUserTTL()).Err(); err != nil { // return errors.WithStack(err) // } return nil } // GetBarcodeUser checks if the subject already has barcode and fetches the barcode key if present. func (s *Store) GetBarcodeUser(ctx context.Context, subject string) (barCodeKey string, err error) { key := keyFrom(KeyPrefixBarcodeUser, subject) var doc *Doc if doc, err = s.getDoc(ctx, key); err != nil { return "", err } if doc == nil { return "", errors.New("no barcode issued for this user") } var barcodeUser *BarcodeUser if err = json.Unmarshal(doc.Value, &barcodeUser); err != nil { return "", err } return barcodeUser.Key, nil } func (s *Store) DeleteBarcodeCode(_ context.Context, code string) (err error) { key := keyFrom(KeyPrefixBarcodeCode, code) err = s.RedisDB.Del(key).Err() if err != nil { return errors.Wrap(err, fmt.Sprintf("Failed to delete key '%v'", key)) } return nil } func (s *Store) GetBarcodeCode(_ context.Context, signature string) (barcodeCode *barcode.Code, err error) { key := keyFrom(KeyPrefixBarcodeCode, signature) cmd := s.RedisDB.Get(key) if cmd.Err() == redis.Nil { return nil, errors.WithMessage(cmd.Err(), "barcode code not found") } data, _ := cmd.Bytes() if config.EncryptionEnabled() { encryptedValue, err := crypto.DecryptRedis(string(data), config.EncryptionKey()) if err != nil { return nil, errors.WithMessage(cmd.Err(), "error decrypting barcode code") } err = json.Unmarshal(encryptedValue, &barcodeCode) if err != nil { return nil, errors.WithMessage(err, "barcode schema not valid") } } else { err = json.Unmarshal(data, &barcodeCode) if err != nil { return nil, errors.WithMessage(err, "barcode schema not valid") } } // check if the barcode-code has been used if barcodeCode.Used { return nil, iamErrors.ErrUsedBarcodeCode } return barcodeCode, nil } func (s *Store) CreateBarcode(ctx context.Context, key, secret, subject string) (err error) { key = keyFrom(KeyPrefixBarcode, key) payload := &barcode.Barcode{ Subject: subject, Secret: secret, CreatedAt: time.Now().UTC(), } value, err := json.Marshal(payload) if err != nil { return errors.WithStack(err) } if err := s.createDoc(ctx, key, value, WithExpiration(config.GetBarcodeCodeTTL())); err != nil { return errors.WithStack(err) } return nil } func (s *Store) GetBarcode(ctx context.Context, key string) (barcode *barcode.Barcode, err error) { key = keyFrom(KeyPrefixBarcode, key) var doc *Doc if doc, err = s.getDoc(ctx, key); err != nil { return nil, err } if doc == nil { return nil, errors.New("barcode not found") } data := doc.Value err = json.Unmarshal(data, &barcode) if err != nil { return nil, errors.WithMessage(err, "barcode schema not valid") } return barcode, nil } func (s *Store) DeleteBarcode(ctx context.Context, key string) (err error) { key = keyFrom(KeyPrefixBarcode, key) err = s.deleteDoc(ctx, key) if err != nil { return errors.WithMessage(err, "barcode failed to delete") } return nil } func (s *Store) createOrUpdateBarcodeCode(signature, subject, issuedBy, clientID, barcodeType, challenge string, used bool) (err error) { key := keyFrom(KeyPrefixBarcodeCode, signature) // ToDo (must): update should not need all other fields except the one we want to update. payload := &barcode.Code{ Subject: subject, ClientID: clientID, Used: used, CreatedAt: time.Now().UTC(), IssuedBy: issuedBy, Type: barcodeType, Challenge: challenge, } value, err := json.Marshal(payload) if err != nil { return errors.WithStack(err) } if config.EncryptionEnabled() { encryptedValue, err := crypto.EncryptRedis(value, config.EncryptionKey()) if err != nil { return errors.WithStack(err) } // used SET because, we update this key twice, when issued and used if err := s.RedisDB.Set(key, encryptedValue, config.GetBarcodeCodeTTL()).Err(); err != nil { return errors.WithStack(err) } } else { // used SET because, we update this key twice, when issued and used if err := s.RedisDB.Set(key, string(value), config.GetBarcodeCodeTTL()).Err(); err != nil { return errors.WithStack(err) } } return nil }