| 1 | package directorydata |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "database/sql" |
| 6 | ) |
| 7 | |
| 8 | // pendingRosterAllowed observes the operator-owned window in the roster read |
| 9 | // transaction. The window never renews here; every page checks its absolute end. |
| 10 | func pendingRosterAllowed(ctx context.Context, tx *sql.Tx, route Route, generation string, now int64) bool { |
| 11 | var issued, expires int64 |
| 12 | var owner string |
| 13 | err := tx.QueryRowContext(ctx, `SELECT a.issued_at,a.expires_at,a.owner_ref |
| 14 | FROM verification_active v |
| 15 | JOIN verification_attempts a ON a.operation_id=v.operation_id AND a.consumer_id=v.consumer_id |
| 16 | JOIN directory_operator_consumer_records r ON r.consumer_id=a.consumer_id |
| 17 | JOIN roster_operator_receipts receipt ON receipt.operation_id=r.operation_id |
| 18 | JOIN memberships m ON m.team_id=a.team_id AND m.person_ref=a.owner_ref AND m.role='owner' |
| 19 | WHERE v.consumer_id=? AND a.team_id=? AND a.app=? AND a.origin=? AND a.generation=? |
| 20 | AND r.generation=a.generation AND r.sequence=a.record_sequence AND r.record_digest=a.record_digest |
| 21 | AND typeof(a.issued_at)='integer' AND typeof(a.expires_at)='integer' |
| 22 | AND a.expected_epoch>0 AND typeof(a.expected_epoch)='integer' |
| 23 | AND length(a.authorization_digest)=32 AND typeof(a.authorization_digest)='blob' |
| 24 | AND r.sequence>0 AND length(r.record_digest)=32 AND typeof(r.record_digest)='blob' |
| 25 | AND receipt.consumer_id=r.consumer_id AND receipt.sequence=r.sequence |
| 26 | AND receipt.generation=r.generation AND receipt.record_digest=r.record_digest`, |
| 27 | route.ConsumerID, route.TeamID, route.App, route.Origin, generation).Scan(&issued, &expires, &owner) |
| 28 | return err == nil && validSubject(owner) && now > 0 && issued > 0 && issued <= now && expires > issued && expires-issued <= 600 && now < expires |
| 29 | } |
| 30 | |