package store import ( "context" "atlas9.dev/c/core" "atlas9.dev/c/core/dbi" "atlas9.dev/c/core/iam" "atlas9.dev/c/demo/lib/access" "atlas9.dev/c/demo/lib/mfa" ) type SqliteMfaEnrollmentStore struct { db dbi.DBI guard access.Guard } var _ mfa.EnrollmentStore = (*SqliteMfaEnrollmentStore)(nil) func NewSqliteMfaEnrollmentStore(db dbi.DBI, guard access.Guard) *SqliteMfaEnrollmentStore { return &SqliteMfaEnrollmentStore{db: db, guard: guard} } func (s *SqliteMfaEnrollmentStore) Get(ctx context.Context, userID core.ID) (*mfa.Enrollment, error) { if err := s.self(ctx, userID, mfa.Cap_Mfa_Read); err != nil { return nil, err } var e mfa.Enrollment err := dbi.Get(ctx, s.db, &e, `SELECT user_id, phone, verified FROM mfa_enrollments WHERE user_id = $1`, userID) return &e, err } func (s *SqliteMfaEnrollmentStore) Save(ctx context.Context, e *mfa.Enrollment) error { if err := s.self(ctx, e.UserID, mfa.Cap_Mfa_Write); err != nil { return err } _, err := s.db.Exec(ctx, ` INSERT INTO mfa_enrollments (user_id, phone, verified) VALUES ($1, $2, $3) ON CONFLICT (user_id) DO UPDATE SET phone = $2, verified = $3 `, e.UserID, e.Phone, e.Verified) return err } func (s *SqliteMfaEnrollmentStore) Delete(ctx context.Context, userID core.ID) error { if err := s.self(ctx, userID, mfa.Cap_Mfa_Write); err != nil { return err } _, err := s.db.Exec(ctx, `DELETE FROM mfa_enrollments WHERE user_id = $1`, userID) return err } // self lets a principal manage their own enrollment without a cap, like // profiles. Any other caller — including the pre-auth login flow, whose subject // is empty — must hold cap scoped to the user (a system grant also satisfies it). func (s *SqliteMfaEnrollmentStore) self(ctx context.Context, userID core.ID, cap iam.Cap) error { if userID.String() == iam.GetPrincipal(ctx).Subject { return nil } return s.guard.Check(ctx, cap, userID, "") }