{"id":19164591,"url":"https://github.com/gmhafiz/audit","last_synced_at":"2026-04-28T10:36:28.251Z","repository":{"id":57628941,"uuid":"405817067","full_name":"gmhafiz/audit","owner":"gmhafiz","description":"Audit database INSERT, UPDATE and DELETE by recording who is making that change along with previous and after records.","archived":false,"fork":false,"pushed_at":"2021-09-22T05:31:11.000Z","size":43,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-03T22:16:12.783Z","etag":null,"topics":["audit","golang","security"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gmhafiz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":"audit.go","citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-09-13T03:12:06.000Z","updated_at":"2024-09-12T09:18:11.000Z","dependencies_parsed_at":"2022-09-26T20:10:52.344Z","dependency_job_id":null,"html_url":"https://github.com/gmhafiz/audit","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gmhafiz%2Faudit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gmhafiz%2Faudit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gmhafiz%2Faudit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gmhafiz%2Faudit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gmhafiz","download_url":"https://codeload.github.com/gmhafiz/audit/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240245906,"owners_count":19771029,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["audit","golang","security"],"created_at":"2024-11-09T09:22:57.978Z","updated_at":"2026-04-28T10:36:23.223Z","avatar_url":"https://github.com/gmhafiz.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Introduction\n\nAudit database queries and execs by making use of interceptors from [ngrok/sqlmw](github.com/ngrok/sqlmw).\n\nAny record modifications including `insert`, `update` and `delete` create a new record in the `audits` table. Audit values like \n  - who is making the change\n  - old value\n  - new value \n  - modification time\n\namong others are recorded.\n\nFull list:\n```go\ntype Event struct {\n    Organisation uint64    `db:\"organisation\"` // or tenant\n    ActorID      uint64    `db:\"actor_id\"`\n    TableRowID   uint64    `db:\"table_row_id\"`\n    Table        string    `db:\"table_name\"`\n    Action       Action    `db:\"action\"`\n    OldValues    string    `db:\"old_values\"`\n    NewValues    string    `db:\"new_values\"`\n    HTTPMethod   string    `db:\"http_method\"`\n    URL          string    `db:\"url\"`\n    IPAddress    string    `db:\"ip_address\"`\n    UserAgent    string    `db:\"user_agent\"`\n    CreatedAt    time.Time `db:\"created_at\"`\n}\n```\n\nBoth `old_values` and `new_values` are stored in JSON format. For example:\n\n| id | organisation\\_id | actor\\_id | table\\_row\\_id | table\\_name | action | old\\_values | new\\_values | http\\_method | url | ip\\_address | user\\_agent | created\\_at |\n| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |\n| 42 | 1 | 2 | 15 | users | update | {\"name\":\"test name\",\"id\":\"42\"} | {\"name\":\"changed name\",\"id\":\"42\"} | PUT | /api/v1/user/42 | localhost:8080 | PostmanRuntime/7.28.4 | 2021-09-15 02:10:02 |\n\n\n# Install\n\n    go get github.com/gmhafiz/audit\n    go get github.com/go-sql-driver/mysql\n\n# Usage\n\n1. Open database connection pool and register the database middleware and auditor\n\nCreate a new audit instance\n```go\nauditor, err := audit.NewAudit()\n```\nOptionally, you can customize the audit table name\n```go\nauditor, err := audit.NewAudit(audit.WithTableName(\"other_audit_table_name\")) // only alphanumeric name is accepted \n```\n\nYou can also add a list of tables to be exempted:\n```go\nauditor, err := audit.NewAudit(\n    audit.WithTableName(\"other_audit_table_name\"),\n    audit.WithTableException(\"schema_migrations\", \"other_tables\"),\n)\n```\nAdd the code to where you open database connection:\n```go\npackage database\n\nimport (\n    \"database/sql\"\n    \"log\"\n    \n    \"github.com/gmhafiz/audit\"\n    \"github.com/go-sql-driver/mysql\"\n    \"github.com/ngrok/sqlmw\"\n)\n\nfunc NewDB(dataSourceName string) (*sql.DB, auditor *audit.Auditor) {\n    // initialise auditor\n    auditor, err := audit.NewAudit()\n    if err != nil {\n        log.Fatal(err)\n    }\n   \n    // register sql interceptor and our custom driver\n    databaseDriverName, err := audit.RegisterDriverInterceptor(auditor, \"mysql\")\n    if err != nil {\n        log.Fatal(err)\n    }\n    \n    // open database connection using that driver\n    db, err := sql.Open(databaseDriverName, dataSourceName)\n    if err != nil {\n        log.Fatal(err)\n    }\n```\nAdding your application database instance is compulsory and is done after connection pool is opened. This is used to query and save both old values and new values of the affected record.\n```go\n    err = auditor.SetDB(\n        audit.Sql(db),\n    )\n    if err != nil {\n        log.Fatal(err)\n    }\n   \n    return db, auditor\n}\n```\n\nBy setting the database, the library will create an `audits` table automatically for you. Index creation is left to the user. Often, you would want a composite index of (`table_row_id`, `table_name`) - and possibly a separate `actor_id` index.\n\nFor Mysql:\n```sql\ncreate index audits_table_row_id_table_name_index\n\ton audits (table_row_id, table_name);\ncreate index audits_actor_id_index\n    on audits (actor_id);\n```\n\n2. A middleware is needed to capture current user ID and optionally organisation/tenant ID from the current request context. In order to use it, both user ID and organisation ID must be saved into `context` in `UserID` and `OrganisationID` respectively. These two values are retrieved from JWT or session cookies.\n\n```go\nimport (\n\t\"github.com/gmhafiz/audit/middleware\"\n)\n\nfunc Auth(store *redisstore.RedisStore) Adapter {\n    return func(next http.Handler) http.Handler {\n        return http.HandlerFunc(func (w http.ResponseWriter, r *http.Request) {\n            var organisationID = get session.Values[\"organisationID\"]\n            var userID = session.Values[\"userID\"]\n            \n            ctx := context.WithValue(r.Context(), middleware.OrganisationID, organisationID)\n            ctx = context.WithValue(ctx, middleware.UserID, userID)\n            \n            next.ServeHTTP(w, r.WithContext(ctx))\n        })\n    }\n}\n```\n\n3. Use the provided middleware that captures current user ID and optionally organisation ID by registering it to your router.\n\nYour own `Auth` middleware has to be registered before `middleware.Audit`.\n\n```go\nimport (\n    \"github.com/gmhafiz/audit/middleware\"\n    \"github.com/go-chi/chi/v5\"\n)\n\nfunc router() *chi.Mux {\n    r := chi.NewRouter()\n    r.Use(middleware.Auth)\n    r.Use(middleware.Audit)\n   \n    return r\n}\n```\n\n\n# Test\n\n1. Create an appropriate testing database for each postgres and mysql\n2. Set both `MYSQL_DSN` and `POSTGRES_DSN` in your environment variable.\n\n\n    POSTGRES_DSN=host=0.0.0.0 port=5432d user=users password=password dbname=audit_test sslmode=disable\n    MYSQL_DSN=root:password@tcp(0.0.0.0:3306)/audit_test?parseTime=true\u0026interpolateParams=true\n\n\n3. Run\n\n\n    go test ./...\n\n# Limitations\n\n1. [Table ID](#table-id)\n2. [Hooks](#hooks)\n3. [Login](#login)\n4. [`IN` operator](#IN-operator)\n\n## Table ID\n\nThis library assumes your table ids are in `uint64` format and named `id`,\n\n## Hooks\n\n\n## Login\n\nScenario: Say for every login, you save the time that user last logged in - and you want this to be audited.\n\nRemember that an `Auth` middleware is needed to capture user id (from JWT or session token). This audit library won't be able to capture the IDs because you do not place an `Auth` middleware to logging in a user.\n\nTo work around this, save the IDs manually before making a call to set user's last login time.\n```go\nfunc (u *userService) Login(ctx context.Context, loginReq *LoginRequest) (*models.User, error) {\n    user, err := u.repository.Get(ctx, loginReq)\n    if err != nil {\n        return nil, err\n    }\n    isValidPassword, err := checkValidPassword(loginReq.Password, user.Password)\n    if err != nil {\n        return nil, err\n    }\n\t\n\t// once you've checked that the login is valid, you may save the IDs\n    ctx = context.WithValue(ctx, \"userID\", user.ID)\n\n    // Finally, you may set the time this user last login. The hooks will be applied since you are making an `update` database operation.\n    err = u.repository.SetLastLogin(ctx, user)\n\n    return user, nil\n}\n```\n\n## IN Operator\n\nCurrently, this audit package doesn't parse `IN` operator correctly. \n\n# Credit\n\n - https://github.com/qustavo/sqlhooks/ for the SQL hooks.\n - https://github.com/owen-it/laravel-auditing for the motivation (no similar library has existed for Go) and schema inspiration.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgmhafiz%2Faudit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgmhafiz%2Faudit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgmhafiz%2Faudit/lists"}