tenseleyflow/shithub / 3baee64

Browse files

users/sqlc: device-authorization queries

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
3baee64fced62cb6165439c26971fce298607170
Parents
76801b1
Tree
ed2bfb0

4 changed files

StatusFile+-
A internal/users/queries/device_authorizations.sql 60 0
A internal/users/sqlc/device_authorizations.sql.go 185 0
M internal/users/sqlc/models.go 16 0
M internal/users/sqlc/querier.go 18 0
internal/users/queries/device_authorizations.sqladded
@@ -0,0 +1,60 @@
1
+-- SPDX-License-Identifier: AGPL-3.0-or-later
2
+
3
+-- name: InsertDeviceAuthorization :one
4
+INSERT INTO device_authorizations (
5
+    device_code_hash, user_code, client_id, scopes,
6
+    interval_seconds, expires_at
7
+) VALUES ($1, $2, $3, $4, $5, $6)
8
+RETURNING id, device_code_hash, user_code, client_id, scopes, user_id,
9
+          approved_at, denied_at, issued_token_id, interval_seconds,
10
+          expires_at, last_polled_at, created_at;
11
+
12
+-- name: GetDeviceAuthorizationByCodeHash :one
13
+-- Hot path for the polling /access_token endpoint. The middleware
14
+-- enforces interval_seconds via last_polled_at downstream.
15
+SELECT id, device_code_hash, user_code, client_id, scopes, user_id,
16
+       approved_at, denied_at, issued_token_id, interval_seconds,
17
+       expires_at, last_polled_at, created_at
18
+FROM device_authorizations
19
+WHERE device_code_hash = $1;
20
+
21
+-- name: GetDeviceAuthorizationByUserCode :one
22
+-- Lookup path for the verification page. Returns even non-pending rows
23
+-- so the handler can render a clean "already approved" / "expired" page
24
+-- instead of a generic 404.
25
+SELECT id, device_code_hash, user_code, client_id, scopes, user_id,
26
+       approved_at, denied_at, issued_token_id, interval_seconds,
27
+       expires_at, last_polled_at, created_at
28
+FROM device_authorizations
29
+WHERE user_code = $1;
30
+
31
+-- name: ApproveDeviceAuthorization :exec
32
+-- Records the user's approval and links the freshly minted PAT.
33
+-- Idempotency is preserved by the caller — the orchestrator only
34
+-- calls this once per row.
35
+UPDATE device_authorizations
36
+SET user_id = $2,
37
+    approved_at = now(),
38
+    issued_token_id = $3
39
+WHERE id = $1
40
+  AND approved_at IS NULL
41
+  AND denied_at IS NULL
42
+  AND expires_at > now();
43
+
44
+-- name: DenyDeviceAuthorization :exec
45
+UPDATE device_authorizations
46
+SET denied_at = now()
47
+WHERE id = $1
48
+  AND approved_at IS NULL
49
+  AND denied_at IS NULL;
50
+
51
+-- name: TouchDeviceAuthorizationPoll :exec
52
+UPDATE device_authorizations
53
+SET last_polled_at = now()
54
+WHERE id = $1;
55
+
56
+-- name: DeleteExpiredDeviceAuthorizations :exec
57
+-- Janitor invocation: a small forensics window past expiry is fine,
58
+-- but eventually drop the row so the user_code index stays small.
59
+DELETE FROM device_authorizations
60
+WHERE expires_at < now() - interval '24 hours';
internal/users/sqlc/device_authorizations.sql.goadded
@@ -0,0 +1,185 @@
1
+// Code generated by sqlc. DO NOT EDIT.
2
+// versions:
3
+//   sqlc v1.31.1
4
+// source: device_authorizations.sql
5
+
6
+package usersdb
7
+
8
+import (
9
+	"context"
10
+
11
+	"github.com/jackc/pgx/v5/pgtype"
12
+)
13
+
14
+const approveDeviceAuthorization = `-- name: ApproveDeviceAuthorization :exec
15
+UPDATE device_authorizations
16
+SET user_id = $2,
17
+    approved_at = now(),
18
+    issued_token_id = $3
19
+WHERE id = $1
20
+  AND approved_at IS NULL
21
+  AND denied_at IS NULL
22
+  AND expires_at > now()
23
+`
24
+
25
+type ApproveDeviceAuthorizationParams struct {
26
+	ID            int64
27
+	UserID        pgtype.Int8
28
+	IssuedTokenID pgtype.Int8
29
+}
30
+
31
+// Records the user's approval and links the freshly minted PAT.
32
+// Idempotency is preserved by the caller — the orchestrator only
33
+// calls this once per row.
34
+func (q *Queries) ApproveDeviceAuthorization(ctx context.Context, db DBTX, arg ApproveDeviceAuthorizationParams) error {
35
+	_, err := db.Exec(ctx, approveDeviceAuthorization, arg.ID, arg.UserID, arg.IssuedTokenID)
36
+	return err
37
+}
38
+
39
+const deleteExpiredDeviceAuthorizations = `-- name: DeleteExpiredDeviceAuthorizations :exec
40
+DELETE FROM device_authorizations
41
+WHERE expires_at < now() - interval '24 hours'
42
+`
43
+
44
+// Janitor invocation: a small forensics window past expiry is fine,
45
+// but eventually drop the row so the user_code index stays small.
46
+func (q *Queries) DeleteExpiredDeviceAuthorizations(ctx context.Context, db DBTX) error {
47
+	_, err := db.Exec(ctx, deleteExpiredDeviceAuthorizations)
48
+	return err
49
+}
50
+
51
+const denyDeviceAuthorization = `-- name: DenyDeviceAuthorization :exec
52
+UPDATE device_authorizations
53
+SET denied_at = now()
54
+WHERE id = $1
55
+  AND approved_at IS NULL
56
+  AND denied_at IS NULL
57
+`
58
+
59
+func (q *Queries) DenyDeviceAuthorization(ctx context.Context, db DBTX, id int64) error {
60
+	_, err := db.Exec(ctx, denyDeviceAuthorization, id)
61
+	return err
62
+}
63
+
64
+const getDeviceAuthorizationByCodeHash = `-- name: GetDeviceAuthorizationByCodeHash :one
65
+SELECT id, device_code_hash, user_code, client_id, scopes, user_id,
66
+       approved_at, denied_at, issued_token_id, interval_seconds,
67
+       expires_at, last_polled_at, created_at
68
+FROM device_authorizations
69
+WHERE device_code_hash = $1
70
+`
71
+
72
+// Hot path for the polling /access_token endpoint. The middleware
73
+// enforces interval_seconds via last_polled_at downstream.
74
+func (q *Queries) GetDeviceAuthorizationByCodeHash(ctx context.Context, db DBTX, deviceCodeHash []byte) (DeviceAuthorization, error) {
75
+	row := db.QueryRow(ctx, getDeviceAuthorizationByCodeHash, deviceCodeHash)
76
+	var i DeviceAuthorization
77
+	err := row.Scan(
78
+		&i.ID,
79
+		&i.DeviceCodeHash,
80
+		&i.UserCode,
81
+		&i.ClientID,
82
+		&i.Scopes,
83
+		&i.UserID,
84
+		&i.ApprovedAt,
85
+		&i.DeniedAt,
86
+		&i.IssuedTokenID,
87
+		&i.IntervalSeconds,
88
+		&i.ExpiresAt,
89
+		&i.LastPolledAt,
90
+		&i.CreatedAt,
91
+	)
92
+	return i, err
93
+}
94
+
95
+const getDeviceAuthorizationByUserCode = `-- name: GetDeviceAuthorizationByUserCode :one
96
+SELECT id, device_code_hash, user_code, client_id, scopes, user_id,
97
+       approved_at, denied_at, issued_token_id, interval_seconds,
98
+       expires_at, last_polled_at, created_at
99
+FROM device_authorizations
100
+WHERE user_code = $1
101
+`
102
+
103
+// Lookup path for the verification page. Returns even non-pending rows
104
+// so the handler can render a clean "already approved" / "expired" page
105
+// instead of a generic 404.
106
+func (q *Queries) GetDeviceAuthorizationByUserCode(ctx context.Context, db DBTX, userCode string) (DeviceAuthorization, error) {
107
+	row := db.QueryRow(ctx, getDeviceAuthorizationByUserCode, userCode)
108
+	var i DeviceAuthorization
109
+	err := row.Scan(
110
+		&i.ID,
111
+		&i.DeviceCodeHash,
112
+		&i.UserCode,
113
+		&i.ClientID,
114
+		&i.Scopes,
115
+		&i.UserID,
116
+		&i.ApprovedAt,
117
+		&i.DeniedAt,
118
+		&i.IssuedTokenID,
119
+		&i.IntervalSeconds,
120
+		&i.ExpiresAt,
121
+		&i.LastPolledAt,
122
+		&i.CreatedAt,
123
+	)
124
+	return i, err
125
+}
126
+
127
+const insertDeviceAuthorization = `-- name: InsertDeviceAuthorization :one
128
+
129
+INSERT INTO device_authorizations (
130
+    device_code_hash, user_code, client_id, scopes,
131
+    interval_seconds, expires_at
132
+) VALUES ($1, $2, $3, $4, $5, $6)
133
+RETURNING id, device_code_hash, user_code, client_id, scopes, user_id,
134
+          approved_at, denied_at, issued_token_id, interval_seconds,
135
+          expires_at, last_polled_at, created_at
136
+`
137
+
138
+type InsertDeviceAuthorizationParams struct {
139
+	DeviceCodeHash  []byte
140
+	UserCode        string
141
+	ClientID        string
142
+	Scopes          []string
143
+	IntervalSeconds int32
144
+	ExpiresAt       pgtype.Timestamptz
145
+}
146
+
147
+// SPDX-License-Identifier: AGPL-3.0-or-later
148
+func (q *Queries) InsertDeviceAuthorization(ctx context.Context, db DBTX, arg InsertDeviceAuthorizationParams) (DeviceAuthorization, error) {
149
+	row := db.QueryRow(ctx, insertDeviceAuthorization,
150
+		arg.DeviceCodeHash,
151
+		arg.UserCode,
152
+		arg.ClientID,
153
+		arg.Scopes,
154
+		arg.IntervalSeconds,
155
+		arg.ExpiresAt,
156
+	)
157
+	var i DeviceAuthorization
158
+	err := row.Scan(
159
+		&i.ID,
160
+		&i.DeviceCodeHash,
161
+		&i.UserCode,
162
+		&i.ClientID,
163
+		&i.Scopes,
164
+		&i.UserID,
165
+		&i.ApprovedAt,
166
+		&i.DeniedAt,
167
+		&i.IssuedTokenID,
168
+		&i.IntervalSeconds,
169
+		&i.ExpiresAt,
170
+		&i.LastPolledAt,
171
+		&i.CreatedAt,
172
+	)
173
+	return i, err
174
+}
175
+
176
+const touchDeviceAuthorizationPoll = `-- name: TouchDeviceAuthorizationPoll :exec
177
+UPDATE device_authorizations
178
+SET last_polled_at = now()
179
+WHERE id = $1
180
+`
181
+
182
+func (q *Queries) TouchDeviceAuthorizationPoll(ctx context.Context, db DBTX, id int64) error {
183
+	_, err := db.Exec(ctx, touchDeviceAuthorizationPoll, id)
184
+	return err
185
+}
internal/users/sqlc/models.gomodified
@@ -1889,6 +1889,22 @@ type CodeSearchPath struct {
18891889
 	Tsv     interface{}
18901890
 }
18911891
 
1892
+type DeviceAuthorization struct {
1893
+	ID              int64
1894
+	DeviceCodeHash  []byte
1895
+	UserCode        string
1896
+	ClientID        string
1897
+	Scopes          []string
1898
+	UserID          pgtype.Int8
1899
+	ApprovedAt      pgtype.Timestamptz
1900
+	DeniedAt        pgtype.Timestamptz
1901
+	IssuedTokenID   pgtype.Int8
1902
+	IntervalSeconds int32
1903
+	ExpiresAt       pgtype.Timestamptz
1904
+	LastPolledAt    pgtype.Timestamptz
1905
+	CreatedAt       pgtype.Timestamptz
1906
+}
1907
+
18921908
 type DomainEvent struct {
18931909
 	ID          int64
18941910
 	ActorUserID pgtype.Int8
internal/users/sqlc/querier.gomodified
@@ -11,6 +11,10 @@ import (
1111
 )
1212
 
1313
 type Querier interface {
14
+	// Records the user's approval and links the freshly minted PAT.
15
+	// Idempotency is preserved by the caller — the orchestrator only
16
+	// calls this once per row.
17
+	ApproveDeviceAuthorization(ctx context.Context, db DBTX, arg ApproveDeviceAuthorizationParams) error
1418
 	// SPDX-License-Identifier: AGPL-3.0-or-later
1519
 	// Increments the hit counter for (scope, identifier). When the existing
1620
 	// window is older than the supplied window-start cutoff, resets to 1 and
@@ -47,6 +51,9 @@ type Querier interface {
4751
 	CreateUser(ctx context.Context, db DBTX, arg CreateUserParams) (User, error)
4852
 	// SPDX-License-Identifier: AGPL-3.0-or-later
4953
 	CreateUserEmail(ctx context.Context, db DBTX, arg CreateUserEmailParams) (UserEmail, error)
54
+	// Janitor invocation: a small forensics window past expiry is fine,
55
+	// but eventually drop the row so the user_code index stays small.
56
+	DeleteExpiredDeviceAuthorizations(ctx context.Context, db DBTX) error
5057
 	DeleteExpiredEmailVerifications(ctx context.Context, db DBTX) error
5158
 	DeleteExpiredPasswordResets(ctx context.Context, db DBTX) error
5259
 	// Scoped delete: caller must pass owning user_id. Refuses to delete
@@ -58,6 +65,14 @@ type Querier interface {
5865
 	// handler can never delete keys it doesn't own.
5966
 	DeleteUserSSHKey(ctx context.Context, db DBTX, arg DeleteUserSSHKeyParams) (int64, error)
6067
 	DeleteUserTOTP(ctx context.Context, db DBTX, userID int64) error
68
+	DenyDeviceAuthorization(ctx context.Context, db DBTX, id int64) error
69
+	// Hot path for the polling /access_token endpoint. The middleware
70
+	// enforces interval_seconds via last_polled_at downstream.
71
+	GetDeviceAuthorizationByCodeHash(ctx context.Context, db DBTX, deviceCodeHash []byte) (DeviceAuthorization, error)
72
+	// Lookup path for the verification page. Returns even non-pending rows
73
+	// so the handler can render a clean "already approved" / "expired" page
74
+	// instead of a generic 404.
75
+	GetDeviceAuthorizationByUserCode(ctx context.Context, db DBTX, userCode string) (DeviceAuthorization, error)
6176
 	GetEmailVerificationByTokenHash(ctx context.Context, db DBTX, tokenHash []byte) (EmailVerification, error)
6277
 	GetPasswordResetByTokenHash(ctx context.Context, db DBTX, tokenHash []byte) (PasswordReset, error)
6378
 	GetUserByID(ctx context.Context, db DBTX, id int64) (User, error)
@@ -83,6 +98,8 @@ type Querier interface {
8398
 	// SPDX-License-Identifier: AGPL-3.0-or-later
8499
 	InsertAuditLog(ctx context.Context, db DBTX, arg InsertAuditLogParams) error
85100
 	// SPDX-License-Identifier: AGPL-3.0-or-later
101
+	InsertDeviceAuthorization(ctx context.Context, db DBTX, arg InsertDeviceAuthorizationParams) (DeviceAuthorization, error)
102
+	// SPDX-License-Identifier: AGPL-3.0-or-later
86103
 	InsertRecoveryCode(ctx context.Context, db DBTX, arg InsertRecoveryCodeParams) error
87104
 	// SPDX-License-Identifier: AGPL-3.0-or-later
88105
 	InsertUserSSHKey(ctx context.Context, db DBTX, arg InsertUserSSHKeyParams) (UserSshKey, error)
@@ -133,6 +150,7 @@ type Querier interface {
133150
 	SetVerificationToken(ctx context.Context, db DBTX, arg SetVerificationTokenParams) error
134151
 	SoftDeleteUser(ctx context.Context, db DBTX, id int64) error
135152
 	SuspendUser(ctx context.Context, db DBTX, arg SuspendUserParams) error
153
+	TouchDeviceAuthorizationPoll(ctx context.Context, db DBTX, id int64) error
136154
 	TouchSSHKeyLastUsed(ctx context.Context, db DBTX, arg TouchSSHKeyLastUsedParams) error
137155
 	TouchUserLastLogin(ctx context.Context, db DBTX, id int64) error
138156
 	TouchUserTokenLastUsed(ctx context.Context, db DBTX, arg TouchUserTokenLastUsedParams) error