| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124 |
- // Copyright 2023 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package auth
-
- import (
- "context"
- "crypto/sha256"
- "crypto/subtle"
- "encoding/hex"
- "errors"
- "strings"
- "time"
-
- auth_model "code.gitea.io/gitea/models/auth"
- "code.gitea.io/gitea/modules/setting"
- "code.gitea.io/gitea/modules/timeutil"
- "code.gitea.io/gitea/modules/util"
- )
-
- // Based on https://paragonie.com/blog/2015/04/secure-authentication-php-with-long-term-persistence#secure-remember-me-cookies
-
- // The auth token consists of two parts: ID and token hash
- // Every device login creates a new auth token with an individual id and hash.
- // If a device uses the token to login into the instance, a fresh token gets generated which has the same id but a new hash.
-
- var (
- ErrAuthTokenInvalidFormat = util.NewInvalidArgumentErrorf("auth token has an invalid format")
- ErrAuthTokenExpired = util.NewInvalidArgumentErrorf("auth token has expired")
- ErrAuthTokenInvalidHash = util.NewInvalidArgumentErrorf("auth token is invalid")
- )
-
- func CheckAuthToken(ctx context.Context, value string) (*auth_model.AuthToken, error) {
- if len(value) == 0 {
- return nil, nil
- }
-
- parts := strings.SplitN(value, ":", 2)
- if len(parts) != 2 {
- return nil, ErrAuthTokenInvalidFormat
- }
-
- t, err := auth_model.GetAuthTokenByID(ctx, parts[0])
- if err != nil {
- if errors.Is(err, util.ErrNotExist) {
- return nil, ErrAuthTokenExpired
- }
- return nil, err
- }
-
- if t.ExpiresUnix < timeutil.TimeStampNow() {
- return nil, ErrAuthTokenExpired
- }
-
- hashedToken := sha256.Sum256([]byte(parts[1]))
-
- if subtle.ConstantTimeCompare([]byte(t.TokenHash), []byte(hex.EncodeToString(hashedToken[:]))) == 0 {
- // If an attacker steals a token and uses the token to create a new session the hash gets updated.
- // When the victim uses the old token the hashes don't match anymore and the victim should be notified about the compromised token.
- return nil, ErrAuthTokenInvalidHash
- }
-
- return t, nil
- }
-
- func RegenerateAuthToken(ctx context.Context, t *auth_model.AuthToken) (*auth_model.AuthToken, string, error) {
- token, hash, err := generateTokenAndHash()
- if err != nil {
- return nil, "", err
- }
-
- newToken := &auth_model.AuthToken{
- ID: t.ID,
- TokenHash: hash,
- UserID: t.UserID,
- ExpiresUnix: timeutil.TimeStampNow().AddDuration(time.Duration(setting.LogInRememberDays*24) * time.Hour),
- }
-
- if err := auth_model.UpdateAuthTokenByID(ctx, newToken); err != nil {
- return nil, "", err
- }
-
- return newToken, token, nil
- }
-
- func CreateAuthTokenForUserID(ctx context.Context, userID int64) (*auth_model.AuthToken, string, error) {
- t := &auth_model.AuthToken{
- UserID: userID,
- ExpiresUnix: timeutil.TimeStampNow().AddDuration(time.Duration(setting.LogInRememberDays*24) * time.Hour),
- }
-
- var err error
- t.ID, err = util.CryptoRandomString(10)
- if err != nil {
- return nil, "", err
- }
-
- token, hash, err := generateTokenAndHash()
- if err != nil {
- return nil, "", err
- }
-
- t.TokenHash = hash
-
- if err := auth_model.InsertAuthToken(ctx, t); err != nil {
- return nil, "", err
- }
-
- return t, token, nil
- }
-
- func generateTokenAndHash() (string, string, error) {
- buf, err := util.CryptoRandomBytes(32)
- if err != nil {
- return "", "", err
- }
-
- token := hex.EncodeToString(buf)
-
- hashedToken := sha256.Sum256([]byte(token))
-
- return token, hex.EncodeToString(hashedToken[:]), nil
- }
|