Authentication

The main logic is located in the system/jwt.go file.

JWT-Based Authentication

  • JWT Authentication: The system uses JSON Web Tokens (JWT) with refresh token rotation for managing user sessions.
  • Encryption: JWTs are encrypted using EdDSA for enhanced security.
  • Server-Side Security: As much logic as possible is handled on the server side to maintain security.
  • Token can be invalidated: Refresh tokens allow generating new JWTs and can be manually invalidated by clearing the user_id in the user_tokens table.
  • Transport:
    • For HTTP, the tokens are sent in the authorization header as Bearer tokens.
    • For gRPC, the tokens are sent in the metadata as authorization tokens.
  • Key Management:
    • Private/Public Key Pair: The system uses a private/public key pair for token validation.
    • Key Generation: To generate new keys, run the script:
cd scripts && sh key.sh

Remember to change the JWT_KEY environment variable to the new public key if you are using the Next.js implementation!

Generating Tokens

jwtToken, jwtRefreshToken, err := system.GenerateTokens(refreshToken.Id, user.Id, user.Access, system.GetAccessRoutes(user.Access), user.Avatar, user.Email, subscriptionActive)
if err != nil {
    return nil, system.ErrUnauthorized{Err: fmt.Errorf("Error generating JWT token: %w", err)}
}

Validating Tokens

user, err := system.ValidateToken(token)
if err != nil {
    return system.ErrUnauthorized{Err: err}
}

Next.js specific implementation

Because of the limitaions of middleware in Next.js (Edge environment), the public key is stored in the environment variable JWT_KEY as string.

Authorization

The main logic is located in the system/access.go file.

Role-Based Access Control (RBAC)

  • The main authorization mechanism is based on Discord permissions.
  • Each user has a set of permissions that are checked against the required permissions for a given action.
  • Permissions are stored in a variable-length integer serialized into a string, and are calculated using bitwise operations.
  • This allows for a high level of granularity in permissions, a very low storage overhead, and fast permission checks.

Permissions Constants

const (
	READ_NOTES  int64 = 0x0000000000000001
	INSERT_NOTE int64 = 0x0000000000000002
	UPDATE_NOTE int64 = 0x0000000000000004
	DELETE_NOTE int64 = 0x0000000000000008

	READ_PAYMENTS  int64 = 0x0000000000000010
	CREATE_PAYMENT int64 = 0x0000000000000020

	READ_EMAILS int64 = 0x0000000000000040
	SEND_EMAIL  int64 = 0x0000000000000080

	READ_FILES    int64 = 0x0000000000000100
	UPLOAD_FILE   int64 = 0x0000000000000200
	DOWNLOAD_FILE int64 = 0x0000000000000400
	DELETE_FILE   int64 = 0x0000000000000800
)

Checking Permissions

access := system.NewAccess(user.Access)
if !access.HasAccess(system.READ_NOTES) {
    return system.ErrForbidden{Message: "Access denied"}
}

Attribute-Based Access Control (ABAC)

  • The system also supports Attribute-Based Access Control (ABAC) for more complex access control scenarios.
  • Define policies in the CheckUserAttr function:
func CheckUserAttr(access int64, userAttr *UserAttr) bool {
	if access == UPDATE_NOTE {
		return userAttr.Department == "IT" && userAttr.Position == "Developer"
	}
	if access == DELETE_NOTE {
		return userAttr.Department == "IT" && userAttr.Position == "Admin"
	}
	return true
}

And then check the access:

if !system.NewAccessWithAttr(user.Access, userAttr).HasAccess(system.UPDATE_NOTE) {
    return system.ErrForbidden{Message: "Access denied"}
}

How to Use

Combine the two mechanisms to create a secure and flexible access control system. The flow start with extracting the token from the request, either from the authorization header or the metadata. Then, in the service layer, validate the token and check the access:

user, err := system.ValidateToken(token)
if err != nil {
    return system.ErrUnauthorized{Err: err}
}
access := system.NewAccess(user.Access)
if !access.HasAccess(system.READ_NOTES) {
    return system.ErrForbidden{Message: "Access denied"}
}

Need help?

Visit our discord server to ask any questions, make suggestions and give feedback :).

https://discord.gg/EdSZbQbRyJ