···
4
+
"Coves/internal/atproto/auth"
"Coves/internal/core/users"
18
+
"github.com/golang-jwt/jwt/v5"
// createTestUser creates a test user in the database for use in integration tests
···
return sessionResp.AccessJwt, sessionResp.DID, nil
99
+
// createSimpleTestJWT creates a minimal JWT for testing (Phase 1 - no signature)
100
+
// In production, this would be a real OAuth token from PDS with proper signatures
101
+
func createSimpleTestJWT(userDID string) string {
102
+
// Create minimal JWT claims using RegisteredClaims
103
+
// Use userDID as issuer since we don't have a proper PDS DID for testing
104
+
claims := auth.Claims{
105
+
RegisteredClaims: jwt.RegisteredClaims{
107
+
Issuer: userDID, // Use DID as issuer for testing (valid per atProto)
108
+
Audience: jwt.ClaimStrings{"did:web:test.coves.social"},
109
+
IssuedAt: jwt.NewNumericDate(time.Now()),
110
+
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
112
+
Scope: "com.atproto.access",
115
+
// For Phase 1 testing, we create an unsigned JWT
116
+
// The middleware is configured with skipVerify=true for testing
117
+
header := map[string]interface{}{
122
+
headerJSON, _ := json.Marshal(header)
123
+
claimsJSON, _ := json.Marshal(claims)
125
+
// Base64url encode (without padding)
126
+
headerB64 := base64.RawURLEncoding.EncodeToString(headerJSON)
127
+
claimsB64 := base64.RawURLEncoding.EncodeToString(claimsJSON)
129
+
// For "alg: none", signature is empty
130
+
return headerB64 + "." + claimsB64 + "."
133
+
// generateTID generates a simple timestamp-based identifier for testing
134
+
// In production, PDS generates proper TIDs
135
+
func generateTID() string {
136
+
return fmt.Sprintf("3k%d", time.Now().UnixNano()/1000)
139
+
// createPDSAccount creates a new account on PDS and returns access token + DID
140
+
// This is used for E2E tests that need real PDS accounts
141
+
func createPDSAccount(pdsURL, handle, email, password string) (accessToken, did string, err error) {
142
+
// Call com.atproto.server.createAccount
143
+
reqBody := map[string]string{
146
+
"password": password,
149
+
reqJSON, marshalErr := json.Marshal(reqBody)
150
+
if marshalErr != nil {
151
+
return "", "", fmt.Errorf("failed to marshal account request: %w", marshalErr)
154
+
resp, httpErr := http.Post(
155
+
pdsURL+"/xrpc/com.atproto.server.createAccount",
156
+
"application/json",
157
+
bytes.NewBuffer(reqJSON),
159
+
if httpErr != nil {
160
+
return "", "", fmt.Errorf("failed to create account: %w", httpErr)
162
+
defer func() { _ = resp.Body.Close() }()
164
+
if resp.StatusCode != http.StatusOK {
165
+
body, readErr := io.ReadAll(resp.Body)
166
+
if readErr != nil {
167
+
return "", "", fmt.Errorf("account creation failed (status %d, failed to read body: %w)", resp.StatusCode, readErr)
169
+
return "", "", fmt.Errorf("account creation failed (status %d): %s", resp.StatusCode, string(body))
172
+
var accountResp struct {
173
+
AccessJwt string `json:"accessJwt"`
174
+
DID string `json:"did"`
177
+
if decodeErr := json.NewDecoder(resp.Body).Decode(&accountResp); decodeErr != nil {
178
+
return "", "", fmt.Errorf("failed to decode account response: %w", decodeErr)
181
+
return accountResp.AccessJwt, accountResp.DID, nil
184
+
// writePDSRecord writes a record to PDS via com.atproto.repo.createRecord
185
+
// Returns the AT-URI and CID of the created record
186
+
func writePDSRecord(pdsURL, accessToken, repo, collection, rkey string, record interface{}) (uri, cid string, err error) {
187
+
reqBody := map[string]interface{}{
189
+
"collection": collection,
193
+
// If rkey is provided, include it
195
+
reqBody["rkey"] = rkey
198
+
reqJSON, marshalErr := json.Marshal(reqBody)
199
+
if marshalErr != nil {
200
+
return "", "", fmt.Errorf("failed to marshal record request: %w", marshalErr)
203
+
req, reqErr := http.NewRequest("POST", pdsURL+"/xrpc/com.atproto.repo.createRecord", bytes.NewBuffer(reqJSON))
205
+
return "", "", fmt.Errorf("failed to create request: %w", reqErr)
208
+
req.Header.Set("Content-Type", "application/json")
209
+
req.Header.Set("Authorization", "Bearer "+accessToken)
211
+
resp, httpErr := http.DefaultClient.Do(req)
212
+
if httpErr != nil {
213
+
return "", "", fmt.Errorf("failed to write record: %w", httpErr)
215
+
defer func() { _ = resp.Body.Close() }()
217
+
if resp.StatusCode != http.StatusOK {
218
+
body, readErr := io.ReadAll(resp.Body)
219
+
if readErr != nil {
220
+
return "", "", fmt.Errorf("record creation failed (status %d, failed to read body: %w)", resp.StatusCode, readErr)
222
+
return "", "", fmt.Errorf("record creation failed (status %d): %s", resp.StatusCode, string(body))
225
+
var recordResp struct {
226
+
URI string `json:"uri"`
227
+
CID string `json:"cid"`
230
+
if decodeErr := json.NewDecoder(resp.Body).Decode(&recordResp); decodeErr != nil {
231
+
return "", "", fmt.Errorf("failed to decode record response: %w", decodeErr)
234
+
return recordResp.URI, recordResp.CID, nil