Initial commit: Open sourcing all of the Maple Open Technologies code.
This commit is contained in:
commit
755d54a99d
2010 changed files with 448675 additions and 0 deletions
96
cloud/maplefile-backend/pkg/security/apikey/generator.go
Normal file
96
cloud/maplefile-backend/pkg/security/apikey/generator.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
package apikey
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// PrefixLive is the prefix for production API keys
|
||||
PrefixLive = "live_sk_"
|
||||
// PrefixTest is the prefix for test/sandbox API keys
|
||||
PrefixTest = "test_sk_"
|
||||
// KeyLength is the length of the random part (40 chars in base64url)
|
||||
KeyLength = 30 // 30 bytes = 40 base64url chars
|
||||
)
|
||||
|
||||
// Generator generates API keys
|
||||
type Generator interface {
|
||||
// Generate creates a new live API key
|
||||
Generate() (string, error)
|
||||
// GenerateTest creates a new test API key
|
||||
GenerateTest() (string, error)
|
||||
}
|
||||
|
||||
type generator struct{}
|
||||
|
||||
// NewGenerator creates a new API key generator
|
||||
func NewGenerator() Generator {
|
||||
return &generator{}
|
||||
}
|
||||
|
||||
// Generate creates a new live API key
|
||||
func (g *generator) Generate() (string, error) {
|
||||
return g.generateWithPrefix(PrefixLive)
|
||||
}
|
||||
|
||||
// GenerateTest creates a new test API key
|
||||
func (g *generator) GenerateTest() (string, error) {
|
||||
return g.generateWithPrefix(PrefixTest)
|
||||
}
|
||||
|
||||
func (g *generator) generateWithPrefix(prefix string) (string, error) {
|
||||
// Generate cryptographically secure random bytes
|
||||
b := make([]byte, KeyLength)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", fmt.Errorf("failed to generate random bytes: %w", err)
|
||||
}
|
||||
|
||||
// Encode to base64url (URL-safe, no padding)
|
||||
key := base64.RawURLEncoding.EncodeToString(b)
|
||||
|
||||
// Remove any special chars and make lowercase for consistency
|
||||
key = strings.Map(func(r rune) rune {
|
||||
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') {
|
||||
return r
|
||||
}
|
||||
return -1 // Remove character
|
||||
}, key)
|
||||
|
||||
// Ensure we have at least 40 characters
|
||||
if len(key) < 40 {
|
||||
// Pad with additional random bytes if needed
|
||||
additional := make([]byte, 10)
|
||||
rand.Read(additional)
|
||||
extraKey := base64.RawURLEncoding.EncodeToString(additional)
|
||||
key += extraKey
|
||||
}
|
||||
|
||||
// Trim to exactly 40 characters
|
||||
key = key[:40]
|
||||
|
||||
return prefix + key, nil
|
||||
}
|
||||
|
||||
// ExtractPrefix extracts the prefix from an API key
|
||||
func ExtractPrefix(apiKey string) string {
|
||||
if len(apiKey) < 13 {
|
||||
return ""
|
||||
}
|
||||
return apiKey[:13] // "live_sk_a1b2" or "test_sk_a1b2"
|
||||
}
|
||||
|
||||
// ExtractLastFour extracts the last 4 characters from an API key
|
||||
func ExtractLastFour(apiKey string) string {
|
||||
if len(apiKey) < 4 {
|
||||
return ""
|
||||
}
|
||||
return apiKey[len(apiKey)-4:]
|
||||
}
|
||||
|
||||
// IsValid checks if an API key has a valid format
|
||||
func IsValid(apiKey string) bool {
|
||||
return strings.HasPrefix(apiKey, PrefixLive) || strings.HasPrefix(apiKey, PrefixTest)
|
||||
}
|
||||
35
cloud/maplefile-backend/pkg/security/apikey/hasher.go
Normal file
35
cloud/maplefile-backend/pkg/security/apikey/hasher.go
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
package apikey
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
// Hasher hashes and verifies API keys using SHA-256
|
||||
type Hasher interface {
|
||||
// Hash creates a deterministic SHA-256 hash of the API key
|
||||
Hash(apiKey string) string
|
||||
// Verify checks if the API key matches the hash using constant-time comparison
|
||||
Verify(apiKey string, hash string) bool
|
||||
}
|
||||
|
||||
type hasher struct{}
|
||||
|
||||
// NewHasher creates a new API key hasher
|
||||
func NewHasher() Hasher {
|
||||
return &hasher{}
|
||||
}
|
||||
|
||||
// Hash creates a deterministic SHA-256 hash of the API key
|
||||
func (h *hasher) Hash(apiKey string) string {
|
||||
hash := sha256.Sum256([]byte(apiKey))
|
||||
return base64.StdEncoding.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// Verify checks if the API key matches the hash using constant-time comparison
|
||||
// This prevents timing attacks
|
||||
func (h *hasher) Verify(apiKey string, expectedHash string) bool {
|
||||
actualHash := h.Hash(apiKey)
|
||||
return subtle.ConstantTimeCompare([]byte(actualHash), []byte(expectedHash)) == 1
|
||||
}
|
||||
11
cloud/maplefile-backend/pkg/security/apikey/provider.go
Normal file
11
cloud/maplefile-backend/pkg/security/apikey/provider.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package apikey
|
||||
|
||||
// ProvideGenerator provides an API key generator for dependency injection
|
||||
func ProvideGenerator() Generator {
|
||||
return NewGenerator()
|
||||
}
|
||||
|
||||
// ProvideHasher provides an API key hasher for dependency injection
|
||||
func ProvideHasher() Hasher {
|
||||
return NewHasher()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue