mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-10-31 14:31:02 +00:00 
			
		
		
		
	- Unify the usage of [`crypto/rand.Read`](https://pkg.go.dev/crypto/rand#Read) to `util.CryptoRandomBytes`. - Refactor `util.CryptoRandomBytes` to never return an error. It is documented by Go, https://go.dev/issue/66821, to always succeed. So if we still receive a error or if the returned bytes read is not equal to the expected bytes to be read we panic (just to be on the safe side). - This simplifies a lot of code to no longer care about error handling. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7453 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: Gusted <postmaster@gusted.xyz> Co-committed-by: Gusted <postmaster@gusted.xyz>
		
			
				
	
	
		
			113 lines
		
	
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			113 lines
		
	
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2023 The Forgejo Authors. All rights reserved.
 | |
| // SPDX-License-Identifier: MIT
 | |
| 
 | |
| package auth
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"crypto/sha256"
 | |
| 	"encoding/hex"
 | |
| 	"fmt"
 | |
| 	"time"
 | |
| 
 | |
| 	"forgejo.org/models/db"
 | |
| 	"forgejo.org/modules/timeutil"
 | |
| 	"forgejo.org/modules/util"
 | |
| )
 | |
| 
 | |
| type AuthorizationPurpose string
 | |
| 
 | |
| var (
 | |
| 	// Used to store long term authorization tokens.
 | |
| 	LongTermAuthorization AuthorizationPurpose = "long_term_authorization"
 | |
| 
 | |
| 	// Used to activate a user account.
 | |
| 	UserActivation AuthorizationPurpose = "user_activation"
 | |
| 
 | |
| 	// Used to reset the password.
 | |
| 	PasswordReset AuthorizationPurpose = "password_reset"
 | |
| )
 | |
| 
 | |
| // Used to activate the specified email address for a user.
 | |
| func EmailActivation(email string) AuthorizationPurpose {
 | |
| 	return AuthorizationPurpose("email_activation:" + email)
 | |
| }
 | |
| 
 | |
| // AuthorizationToken represents a authorization token to a user.
 | |
| type AuthorizationToken struct {
 | |
| 	ID              int64  `xorm:"pk autoincr"`
 | |
| 	UID             int64  `xorm:"INDEX"`
 | |
| 	LookupKey       string `xorm:"INDEX UNIQUE"`
 | |
| 	HashedValidator string
 | |
| 	Purpose         AuthorizationPurpose `xorm:"NOT NULL DEFAULT 'long_term_authorization'"`
 | |
| 	Expiry          timeutil.TimeStamp
 | |
| }
 | |
| 
 | |
| // TableName provides the real table name.
 | |
| func (AuthorizationToken) TableName() string {
 | |
| 	return "forgejo_auth_token"
 | |
| }
 | |
| 
 | |
| func init() {
 | |
| 	db.RegisterModel(new(AuthorizationToken))
 | |
| }
 | |
| 
 | |
| // IsExpired returns if the authorization token is expired.
 | |
| func (authToken *AuthorizationToken) IsExpired() bool {
 | |
| 	return authToken.Expiry.AsLocalTime().Before(time.Now())
 | |
| }
 | |
| 
 | |
| // GenerateAuthToken generates a new authentication token for the given user.
 | |
| // It returns the lookup key and validator values that should be passed to the
 | |
| // user via a long-term cookie.
 | |
| func GenerateAuthToken(ctx context.Context, userID int64, expiry timeutil.TimeStamp, purpose AuthorizationPurpose) (lookupKey, validator string, err error) {
 | |
| 	// Request 64 random bytes. The first 32 bytes will be used for the lookupKey
 | |
| 	// and the other 32 bytes will be used for the validator.
 | |
| 	rBytes := util.CryptoRandomBytes(64)
 | |
| 	hexEncoded := hex.EncodeToString(rBytes)
 | |
| 	validator, lookupKey = hexEncoded[64:], hexEncoded[:64]
 | |
| 
 | |
| 	_, err = db.GetEngine(ctx).Insert(&AuthorizationToken{
 | |
| 		UID:             userID,
 | |
| 		Expiry:          expiry,
 | |
| 		LookupKey:       lookupKey,
 | |
| 		HashedValidator: HashValidator(rBytes[32:]),
 | |
| 		Purpose:         purpose,
 | |
| 	})
 | |
| 	return lookupKey, validator, err
 | |
| }
 | |
| 
 | |
| // FindAuthToken will find a authorization token via the lookup key.
 | |
| func FindAuthToken(ctx context.Context, lookupKey string, purpose AuthorizationPurpose) (*AuthorizationToken, error) {
 | |
| 	var authToken AuthorizationToken
 | |
| 	has, err := db.GetEngine(ctx).Where("lookup_key = ? AND purpose = ?", lookupKey, purpose).Get(&authToken)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	} else if !has {
 | |
| 		return nil, fmt.Errorf("lookup key %q: %w", lookupKey, util.ErrNotExist)
 | |
| 	}
 | |
| 	return &authToken, nil
 | |
| }
 | |
| 
 | |
| // DeleteAuthToken will delete the authorization token.
 | |
| func DeleteAuthToken(ctx context.Context, authToken *AuthorizationToken) error {
 | |
| 	_, err := db.DeleteByBean(ctx, authToken)
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| // DeleteAuthTokenByUser will delete all authorization tokens for the user.
 | |
| func DeleteAuthTokenByUser(ctx context.Context, userID int64) error {
 | |
| 	if userID == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	_, err := db.DeleteByBean(ctx, &AuthorizationToken{UID: userID})
 | |
| 	return err
 | |
| }
 | |
| 
 | |
| // HashValidator will return a hexified hashed version of the validator.
 | |
| func HashValidator(validator []byte) string {
 | |
| 	h := sha256.New()
 | |
| 	h.Write(validator)
 | |
| 	return hex.EncodeToString(h.Sum(nil))
 | |
| }
 |