/
githubmirror
/
photoprism
Обзор
Документация
Войти
/
githubmirror
/
photoprism
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
pkg/rnd/auth.go
210 строк
5 KB
Michael Mayer
Pkg: Harden random token generation in the "rnd" package
07 авг 2026, 18:12
07 авг 2026, 18:12
0321aa6
Код
Авторство
О чём код?
package rnd import ( "fmt" "github.com/photoprism/photoprism/pkg/checksum" ) const ( // SessionIdLength is the default length of session identifiers. SessionIdLength = 64 // AuthTokenLength is the length of auth tokens returned by AuthToken. AuthTokenLength = 48 // JoinTokenLength is the length of cluster join tokens. JoinTokenLength = 24 // AppPasswordLength is the length of generated app passwords. AppPasswordLength = 27 // Separator divides human-friendly token sections. Separator = '-' ) // joinTokenSeparators determines where token separators (hyphens) appear. var joinTokenSeparators = [...]int{7, 16} // AuthToken generates a random hexadecimal character token for authenticating client applications. // // Examples: 9fa8e562564dac91b96881040e98f6719212a1a364e0bb25 func AuthToken() string { b := make([]byte, 24) randomFill(b) return fmt.Sprintf("%x", b) } // IsAuthToken checks if the string represents a valid auth token. func IsAuthToken(s string) bool { if l := len(s); l == AuthTokenLength { return IsHex(s) } return false } // AuthTokenID returns a unique 14 character authentication token ID, e.g. for use in JWTs. func AuthTokenID(prefix string) string { if len(prefix) > 8 { prefix = prefix[:8] } return prefix + Base36(14-len(prefix)) } // AppPassword generates a random, human-friendly authentication token that can also be used as // password replacement for client applications. It is separated by 3 dashes for better readability // and has a total length of 27 characters. // // Example: OXiV72-wTtiL9-d04jO7-X7XP4p func AppPassword() string { b := make([]byte, 0, AppPasswordLength) // Draw all random characters up front so the password is always complete. chars := make([]byte, AppPasswordLength) randomChars(chars, CharsetBase62) for i := range AppPasswordLength { switch { case (i+1)%7 == 0: b = append(b, Separator) case i == AppPasswordLength-1: b = append(b, checksum.Char(b)) return string(b) default: b = append(b, chars[i]) } } return string(b) } // IsAppPassword checks if the string represents a valid app password. func IsAppPassword(s string, verifyChecksum bool) bool { // Verify token length. if len(s) != AppPasswordLength { return false } // Check characters. sep := 0 for _, r := range s { if r == Separator { sep++ } else if (r < '0' || r > '9') && (r < 'A' || r > 'Z') && (r < 'a' || r > 'z') { return false } } // Check number of separators. if sep != AppPasswordLength/7 { return false } else if !verifyChecksum { return true } // Verify token checksum. return s[AppPasswordLength-1] == checksum.Char([]byte(s[:AppPasswordLength-1])) } // JoinToken generates a random, human-friendly cluster join token. // The token has a length of 24 characters and is separated by 2 dashes. // // Example: pGVplw8-eISgkdQN-Mep62nQ func JoinToken() string { token := make([]byte, 0, JoinTokenLength) // Draw all random characters up front so the token is always complete. chars := make([]byte, JoinTokenLength) randomChars(chars, CharsetBase62) for i := range JoinTokenLength - 1 { if isJoinTokenSeparatorIndex(i) { token = append(token, Separator) continue } token = append(token, chars[i]) } token = append(token, checksum.Char(token)) return string(token) } func isJoinTokenSeparatorIndex(i int) bool { for _, pos := range joinTokenSeparators { if i == pos { return true } } return false } // IsJoinToken checks if the string represents a join token. func IsJoinToken(s string, strict bool) bool { // Basic mode: No token, not valid. if s == "" { return false } // Non-strict mode: only enforce minimum length so legacy tokens that were // longer than the auto-generated format continue to work. if !strict { return len(s) >= JoinTokenLength } // Strict validation enforces canonical formatting and checksum. if len(s) != JoinTokenLength { return false } sep := 0 for idx, r := range s { if r == Separator { if !isJoinTokenSeparatorIndex(idx) { return false } sep++ continue } if (r < '0' || r > '9') && (r < 'A' || r > 'Z') && (r < 'a' || r > 'z') { return false } } if sep != len(joinTokenSeparators) { return false } return s[JoinTokenLength-1] == checksum.Char([]byte(s[:JoinTokenLength-1])) } // IsAuthAny checks if the string represents a valid auth token or app password. func IsAuthAny(s string) bool { // Check if string might be a regular auth token. if IsAuthToken(s) { return true } // Check if string might be a human-friendly app password. if IsAppPassword(s, false) { return true } return false } // SessionID returns the hashed session id string. func SessionID(token string) string { return Sha256([]byte(token)) } // IsSessionID checks if the string represents a valid session id. func IsSessionID(id string) bool { if len(id) != SessionIdLength { return false } return IsHex(id) }