Skip to content

Commit

Permalink
audit tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
hummerdmag committed Dec 24, 2024
1 parent 2d2a00b commit 0ceec03
Show file tree
Hide file tree
Showing 12 changed files with 91 additions and 33 deletions.
13 changes: 13 additions & 0 deletions model/server_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type ServerSettings struct {
KeyStorage FileStorageSettings `yaml:"keyStorage" json:"key_storage"`
Config FileStorageSettings `yaml:"-" json:"config"`
Logger LoggerSettings `yaml:"logger" json:"logger"`
Audit AuditSettings `yaml:"audit" json:"audit"`
AdminPanel AdminPanelSettings `yaml:"adminPanel" json:"admin_panel"`
LoginWebApp FileStorageSettings `yaml:"loginWebApp" json:"login_web_app"`
EmailTemplates FileStorageSettings `yaml:"emailTemplates" json:"email_templates"`
Expand Down Expand Up @@ -388,6 +389,18 @@ func HTTPLogDetailing(dumpRequest bool, logType HTTPDetailing) HTTPDetailing {
return logType
}

type TokenRecording string

const (
TokenRecordingNone TokenRecording = "none"
TokenRecordingObfuscated TokenRecording = "obfuscated"
TokenRecordingFull TokenRecording = "full"
)

type AuditSettings struct {
TokenRecording TokenRecording `yaml:"tokenRecording" json:"tokenRecording"`
}

type AdminPanelSettings struct {
Enabled bool `json:"enabled" yaml:"enabled"`
}
Expand Down
5 changes: 3 additions & 2 deletions web/api/2fa.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,9 @@ func (ar *Router) FinalizeTFA() http.HandlerFunc {
}
}

ar.journal(JournalOperationLoginWith2FA,
user.ID, app.ID, r.UserAgent(), user.AccessRole, scopes.Scopes())
ar.audit(AuditOperationLoginWith2FA,
user.ID, app.ID, r.UserAgent(), user.AccessRole, scopes.Scopes(),
result.AccessToken, result.RefreshToken)

ar.server.Storages().User.UpdateLoginMetadata(user.ID)
ar.ServeJSON(w, locale, http.StatusOK, result)
Expand Down
5 changes: 3 additions & 2 deletions web/api/federated_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,9 @@ func (ar *Router) FederatedLoginComplete() http.HandlerFunc {
authResult.CallbackUrl = fsess.CallbackUrl
authResult.Scopes = fsess.Scopes

ar.journal(JournalOperationFederatedLogin,
user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes())
ar.audit(AuditOperationFederatedLogin,
user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes(),
authResult.AccessToken, authResult.RefreshToken)

ar.ServeJSON(w, locale, http.StatusOK, authResult)
}
Expand Down
5 changes: 3 additions & 2 deletions web/api/federated_oidc_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,9 @@ func (ar *Router) OIDCLoginComplete(useSession bool) http.HandlerFunc {
authResult.Scopes = resultScopes.Scopes()
authResult.ProviderData = *providerData

ar.journal(JournalOperationOIDCLogin,
user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes())
ar.audit(AuditOperationOIDCLogin,
user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes(),
authResult.AccessToken, authResult.RefreshToken)

ar.ServeJSON(w, locale, http.StatusOK, authResult)
}
Expand Down
5 changes: 3 additions & 2 deletions web/api/impersonate_as.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,9 @@ func (ar *Router) ImpersonateAs() http.HandlerFunc {
// do not allow refresh for impersonated user
authResult.RefreshToken = ""

ar.journal(JournalOperationImpersonatedAs,
userID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes())
ar.audit(AuditOperationImpersonatedAs,
userID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes(),
authResult.AccessToken, authResult.RefreshToken)

ar.ServeJSON(w, locale, http.StatusOK, authResult)
}
Expand Down
50 changes: 38 additions & 12 deletions web/api/journal.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,36 @@ package api

import (
"github.com/madappgang/identifo/v2/logging"
"github.com/madappgang/identifo/v2/model"
)

type JournalOperation string
type AuditOperation string

const (
JournalOperationLoginWithPassword JournalOperation = "login_with_password"
JournalOperationLoginWithPhone JournalOperation = "login_with_phone"
JournalOperationLoginWith2FA JournalOperation = "login_with_2fa"
JournalOperationRefreshToken JournalOperation = "refresh_token"
JournalOperationOIDCLogin JournalOperation = "oidc_login"
JournalOperationFederatedLogin JournalOperation = "federated_login"
JournalOperationRegistration JournalOperation = "registration"
JournalOperationLogout JournalOperation = "logout"
JournalOperationImpersonatedAs JournalOperation = "impersonated_as"
AuditOperationLoginWithPassword AuditOperation = "login_with_password"
AuditOperationLoginWithPhone AuditOperation = "login_with_phone"
AuditOperationLoginWith2FA AuditOperation = "login_with_2fa"
AuditOperationRefreshToken AuditOperation = "refresh_token"
AuditOperationOIDCLogin AuditOperation = "oidc_login"
AuditOperationFederatedLogin AuditOperation = "federated_login"
AuditOperationRegistration AuditOperation = "registration"
AuditOperationLogout AuditOperation = "logout"
AuditOperationImpersonatedAs AuditOperation = "impersonated_as"
)

func (ar *Router) journal(
op JournalOperation,
func (ar *Router) audit(
op AuditOperation,
userID, appID, device, accessRole string,
scopes []string,
accessToken, refreshToken string,
) {
iss := ar.server.Services().Token.Issuer()

auditSettings := ar.server.Settings().Audit

accessToken = maskToken(accessToken, auditSettings.TokenRecording)
refreshToken = maskToken(refreshToken, auditSettings.TokenRecording)

// TODO: Create an interface for the audit log
// Implement it for logging to stdout, a database, or a remote service
ar.logger.Info("audit_record",
Expand All @@ -35,5 +42,24 @@ func (ar *Router) journal(
"issuer", iss,
"accessRole", accessRole,
"scopes", scopes,
"accessToken", accessToken,
"refreshToken", refreshToken,
)
}

func maskToken(token string, tokenRecording model.TokenRecording) string {
switch tokenRecording {
case model.TokenRecordingNone:
return "<redacted>"
case model.TokenRecordingObfuscated:
if len(token) < 32 {
return "<short>"
}

return token[:6] + "..." + token[len(token)-6:]
case model.TokenRecordingFull:
return token
default:
return "<redacted>"
}
}
5 changes: 3 additions & 2 deletions web/api/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,9 @@ func (ar *Router) LoginWithPassword() http.HandlerFunc {
return
}

ar.journal(JournalOperationLoginWithPassword,
user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes())
ar.audit(AuditOperationLoginWithPassword,
user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes(),
authResult.AccessToken, authResult.RefreshToken)

ar.ServeJSON(w, locale, http.StatusOK, authResult)
}
Expand Down
5 changes: 3 additions & 2 deletions web/api/logout.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,9 @@ func (ar *Router) Logout() http.HandlerFunc {
}
}

ar.journal(JournalOperationLogout,
accessToken.Subject(), accessToken.Audience(), r.UserAgent(), "", nil)
ar.audit(AuditOperationLogout,
accessToken.Subject(), accessToken.Audience(), r.UserAgent(), "", nil,
accessTokenString, d.RefreshToken)

ar.ServeJSON(w, locale, http.StatusOK, result)
}
Expand Down
7 changes: 4 additions & 3 deletions web/api/phone_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,9 @@ func (ar *Router) PhoneLogin() http.HandlerFunc {
User: user,
}

ar.journal(JournalOperationLoginWithPhone,
user.ID, app.ID, r.UserAgent(), user.AccessRole, scopes.Scopes())
ar.audit(AuditOperationLoginWithPhone,
user.ID, app.ID, r.UserAgent(), user.AccessRole, scopes.Scopes(),
result.AccessToken, result.RefreshToken)

ar.server.Storages().User.UpdateLoginMetadata(user.ID)

Expand Down Expand Up @@ -208,7 +209,7 @@ func (l *PhoneLogin) validateCodeAndPhone() error {

func (l *PhoneLogin) validatePhone() error {
if !model.PhoneRegexp.MatchString(l.PhoneNumber) {
return errors.New("ohone number is not valid")
return errors.New("phone number is not valid")
}
return nil
}
5 changes: 3 additions & 2 deletions web/api/refresh_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,9 @@ func (ar *Router) RefreshTokens() http.HandlerFunc {
}

resultScopes := strings.Split(accessToken.Scopes(), " ")
ar.journal(JournalOperationRefreshToken,
oldRefreshToken.Subject(), app.ID, r.UserAgent(), "", resultScopes)
ar.audit(AuditOperationRefreshToken,
oldRefreshToken.Subject(), app.ID, r.UserAgent(), "", resultScopes,
result.AccessToken, result.RefreshToken)

ar.ServeJSON(w, locale, http.StatusOK, result)
}
Expand Down
5 changes: 3 additions & 2 deletions web/api/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,9 @@ func (ar *Router) RegisterWithPassword() http.HandlerFunc {
return
}

ar.journal(JournalOperationRegistration,
user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes())
ar.audit(AuditOperationRegistration,
user.ID, app.ID, r.UserAgent(), user.AccessRole, resultScopes.Scopes(),
authResult.AccessToken, authResult.RefreshToken)

ar.ServeJSON(w, locale, http.StatusOK, authResult)
}
Expand Down
14 changes: 12 additions & 2 deletions web/api/router.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package api

import (
"context"
"encoding/json"
"fmt"
"log/slog"
Expand Down Expand Up @@ -155,11 +156,20 @@ func (ar *Router) error(w http.ResponseWriter, callerDepth int, locale string, s
}
message := ar.ls.SL(locale, errID, details...)

ar.logger.Error("api error",
logLevel := slog.LevelWarn
if status >= 500 {
logLevel = slog.LevelError
}

ar.logger.Log(
context.Background(),
logLevel,
"api error",
logging.FieldErrorID, errID,
"status", status,
"details", message,
"where", fmt.Sprintf("%v:%d", file, no))
"where", fmt.Sprintf("%v:%d", file, no),
)

// Write generic error response.
w.Header().Set("Content-Type", "application/json")
Expand Down

0 comments on commit 0ceec03

Please sign in to comment.