Skip to content

Commit cb6ab51

Browse files
authored
Merge pull request #44 from Preloading/basic-auth
feat: basic auth
2 parents 154ed2c + 2b75882 commit cb6ab51

4 files changed

Lines changed: 283 additions & 35 deletions

File tree

cryption/authcrypt.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import (
99
"errors"
1010
"io"
1111
"strings"
12+
13+
"golang.org/x/crypto/argon2"
14+
"golang.org/x/crypto/bcrypt"
1215
)
1316

1417
// GenerateKey generates a new AES key and returns it as a base64 encoded string
@@ -141,3 +144,35 @@ func Base64URLDecode(input string) (string, error) {
141144

142145
return string(decoded), nil
143146
}
147+
148+
type PasswordData struct {
149+
Hash string
150+
Salt string
151+
}
152+
153+
// GeneratePasswordHash generates a secure hash of the password using bcrypt, returns hash and salt
154+
func GeneratePasswordHash(password string) (*PasswordData, error) {
155+
// Generate a random salt
156+
salt := make([]byte, 16)
157+
if _, err := rand.Read(salt); err != nil {
158+
return nil, err
159+
}
160+
161+
// Generate hash using bcrypt
162+
hash, err := bcrypt.GenerateFromPassword([]byte(password+base64.StdEncoding.EncodeToString(salt)), bcrypt.DefaultCost)
163+
if err != nil {
164+
return nil, err
165+
}
166+
167+
return &PasswordData{
168+
Hash: base64.StdEncoding.EncodeToString(hash),
169+
Salt: base64.StdEncoding.EncodeToString(salt),
170+
}, nil
171+
}
172+
173+
// DeriveKeyFromPassword generates an encryption key from a password using Argon2 and a provided salt
174+
func DeriveKeyFromPassword(password string, salt string) string {
175+
saltBytes, _ := base64.StdEncoding.DecodeString(salt)
176+
key := argon2.IDKey([]byte(password), saltBytes, 1, 64*1024, 4, 32)
177+
return base64.StdEncoding.EncodeToString(key)
178+
}

db_controller/db_controller.go

Lines changed: 143 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/Preloading/TwitterAPIBridge/config"
1212
authcrypt "github.com/Preloading/TwitterAPIBridge/cryption"
1313
"github.com/google/uuid"
14+
"golang.org/x/crypto/bcrypt"
1415
"gorm.io/driver/mysql"
1516
"gorm.io/driver/postgres"
1617
"gorm.io/driver/sqlite"
@@ -48,18 +49,21 @@ import (
4849

4950
// Token represents the schema for the tokens table
5051
type Token struct {
51-
UserDid string `gorm:"type:string;primaryKey;not null"`
52+
BasicAuthHash string `gorm:"type:string;"` // I am not a fan that i have to store this for basic authentication
53+
BasicAuthUsername string `gorm:"type:string;index"` // Add this field with an index
54+
UserDid string `gorm:"type:string;index;not null"`
5255
UserPDS string `gorm:"type:string;not null"`
53-
TokenUUID string `gorm:"type:string;primaryKey;not null"`
56+
TokenUUID string `gorm:"type:string;primaryKey"`
5457
EncryptedAccessToken string `gorm:"type:string;not null"`
5558
EncryptedRefreshToken string `gorm:"type:string;not null"`
5659
AccessExpiry float64 `gorm:"type:float;not null"`
5760
RefreshExpiry float64 `gorm:"type:float;not null"`
61+
BasicAuthSalt string `gorm:"type:string"`
5862
}
5963

6064
type TwitterIDs struct {
6165
BlueskyID string `gorm:"type:string;not null"`
62-
TwitterID string `gorm:"type:string;primaryKey;not null"` // Ensure this has a unique constraint
66+
TwitterID string `gorm:"type:string;primaryKey;not null"`
6367
ReposterDid *string `gorm:"type:string"`
6468
DateCreated *time.Time `gorm:"type:timestamp"`
6569
}
@@ -187,6 +191,55 @@ func UpdateToken(uuid string, did string, pds string, accessToken string, refres
187191
return &token.TokenUUID, nil
188192
}
189193

194+
// StoreTokenBasic stores a token using basic auth (password)
195+
func StoreTokenBasic(did string, pds string, accessToken string, refreshToken string, username string, password string, accessExpiry float64, refreshExpiry float64) (*string, error) {
196+
passwordData, err := authcrypt.GeneratePasswordHash(password)
197+
if err != nil {
198+
return nil, err
199+
}
200+
201+
// generate a new UUID for the token
202+
uuid, err := uuid.NewRandom()
203+
if err != nil {
204+
return nil, err
205+
}
206+
207+
return UpdateTokenBasic(did, pds, accessToken, refreshToken, accessExpiry, refreshExpiry, username, password, passwordData.Hash, passwordData.Salt, uuid.String())
208+
}
209+
210+
// UpdateTokenBasic updates or creates a token entry using basic auth
211+
func UpdateTokenBasic(did string, pds string, accessToken string, refreshToken string, accessExpiry float64, refreshExpiry float64, username string, password string, passwordHash string, passwordSalt string, uuid string) (*string, error) {
212+
encryptionKey := authcrypt.DeriveKeyFromPassword(password, passwordSalt)
213+
encryptedAccess, err := authcrypt.Encrypt(accessToken, encryptionKey)
214+
if err != nil {
215+
return nil, fmt.Errorf("failed to encrypt access token: %v", err)
216+
}
217+
218+
encryptedRefresh, err := authcrypt.Encrypt(refreshToken, encryptionKey)
219+
if err != nil {
220+
return nil, fmt.Errorf("failed to encrypt refresh token: %v", err)
221+
}
222+
223+
token := Token{
224+
UserDid: did,
225+
UserPDS: pds,
226+
EncryptedAccessToken: encryptedAccess,
227+
EncryptedRefreshToken: encryptedRefresh,
228+
AccessExpiry: accessExpiry,
229+
RefreshExpiry: refreshExpiry,
230+
BasicAuthHash: passwordHash,
231+
BasicAuthSalt: passwordSalt,
232+
BasicAuthUsername: username,
233+
TokenUUID: uuid,
234+
}
235+
236+
if err := db.Create(&token).Error; err != nil {
237+
return nil, err
238+
}
239+
240+
return &token.TokenUUID, nil
241+
}
242+
190243
// GetToken retrieves account data from the database
191244
// @results: accessToken, refreshToken, accessExpiry, refreshExpiry, pds, error
192245

@@ -209,6 +262,93 @@ func GetToken(did string, tokenUUID string, encryptionKey string) (*string, *str
209262
return &accessToken, &refreshToken, &token.AccessExpiry, &token.RefreshExpiry, &token.UserPDS, nil
210263
}
211264

265+
// GetTokenViaBasic retrieves a token using only the password
266+
// @results: accessToken, refreshToken, accessExpiry, refreshExpiry, pds, did, hash, salt, uuid, error
267+
func GetTokenViaBasic(username string, password string) (*string, *string, *float64, *float64, *string, *string, *string, *string, *string, error) {
268+
var tokens []Token
269+
if err := db.Where("basic_auth_username = ?", username).Find(&tokens).Error; err != nil {
270+
return nil, nil, nil, nil, nil, nil, nil, nil, nil, fmt.Errorf("invalid credentials")
271+
}
272+
var token Token
273+
if len(tokens) == 0 {
274+
return nil, nil, nil, nil, nil, nil, nil, nil, nil, fmt.Errorf("invalid credentials")
275+
}
276+
277+
for _, t := range tokens {
278+
if err := bcrypt.CompareHashAndPassword([]byte(token.BasicAuthHash), []byte(password)); err != nil {
279+
token = t
280+
break
281+
}
282+
}
283+
284+
if token.EncryptedAccessToken == "" {
285+
return nil, nil, nil, nil, nil, nil, nil, nil, nil, fmt.Errorf("invalid credentials")
286+
}
287+
288+
encryptionKey := authcrypt.DeriveKeyFromPassword(password, token.BasicAuthSalt)
289+
290+
accessToken, err := authcrypt.Decrypt(token.EncryptedAccessToken, encryptionKey)
291+
if err != nil {
292+
return nil, nil, nil, nil, nil, nil, nil, nil, nil, err
293+
}
294+
295+
refreshToken, err := authcrypt.Decrypt(token.EncryptedRefreshToken, encryptionKey)
296+
if err != nil {
297+
return nil, nil, nil, nil, nil, nil, nil, nil, nil, err
298+
}
299+
300+
return &accessToken, &refreshToken, &token.AccessExpiry, &token.RefreshExpiry, &token.UserPDS, &token.UserDid, &token.BasicAuthHash, &token.BasicAuthSalt, &token.TokenUUID, nil
301+
}
302+
303+
// DeleteToken deletes a token using did and uuid
304+
func DeleteToken(did string, tokenUUID string) error {
305+
result := db.Where("user_did = ? AND token_uuid = ?", did, tokenUUID).Delete(&Token{})
306+
if result.Error != nil {
307+
return result.Error
308+
}
309+
if result.RowsAffected == 0 {
310+
return fmt.Errorf("token not found")
311+
}
312+
return nil
313+
}
314+
315+
// DeleteTokenViaBasic deletes a token using the username and password
316+
func DeleteTokenViaBasic(username string, password string) error {
317+
var tokens []Token
318+
if err := db.Where("basic_auth_username = ?", username).Find(&tokens).Error; err != nil {
319+
return fmt.Errorf("invalid credentials")
320+
}
321+
322+
if len(tokens) == 0 {
323+
return fmt.Errorf("token not found")
324+
}
325+
326+
var foundToken Token
327+
for _, t := range tokens {
328+
if err := bcrypt.CompareHashAndPassword([]byte(t.BasicAuthHash), []byte(password)); err != nil {
329+
foundToken = t
330+
break
331+
}
332+
}
333+
334+
if foundToken.EncryptedAccessToken == "" {
335+
return fmt.Errorf("invalid credentials")
336+
}
337+
338+
result := db.Where("basic_auth_username = ? AND basic_auth_hash = ?",
339+
username, foundToken.BasicAuthHash).Delete(&Token{})
340+
341+
if result.Error != nil {
342+
return result.Error
343+
}
344+
345+
if result.RowsAffected == 0 {
346+
return fmt.Errorf("token not found")
347+
}
348+
349+
return nil
350+
}
351+
212352
// Stores ID data in the database.
213353
// @params: twitterID, blueskyID, dateCreated, reposterDid
214354
// @results: error

0 commit comments

Comments
 (0)