From d51a92f6e13c43c747303a18699f6f4778deaa7c Mon Sep 17 00:00:00 2001 From: valentinkolb Date: Wed, 27 Mar 2024 15:36:45 +0100 Subject: [PATCH] feat: qr-code api, major refactor, objectGUID as user id and group id --- .dockerignore | 5 +- .gitea/workflows/deploy.yaml | 32 ++++++++++ .gitignore | 1 + .gitlab-ci.yml | 15 ----- Dockerfile | 4 ++ Readme.md | 87 ++++++++++++++++++++++++--- go.mod | 13 ++-- go.sum | 4 +- ldapLogin/main.go | 4 +- ldapSync/db.go | 14 ++--- ldapSync/ldapSync.go | 40 +++++++++++-- ldapSync/main.go | 4 +- ldapSync/models.go | 4 +- ldapSync/tables.go | 18 +----- main/main.go | 21 +++++-- qrApi/main.go | 111 +++++++++++++++++++++++++++++++++++ 16 files changed, 306 insertions(+), 71 deletions(-) create mode 100644 .gitea/workflows/deploy.yaml delete mode 100644 .gitlab-ci.yml create mode 100644 qrApi/main.go diff --git a/.dockerignore b/.dockerignore index 028acce..524233e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,6 @@ .env.local .gitignore -.gitlab-ci.yml +.gitea +.pb_data docker-compose.yml -Readme.md +Readme.md \ No newline at end of file diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml new file mode 100644 index 0000000..f23e6a4 --- /dev/null +++ b/.gitea/workflows/deploy.yaml @@ -0,0 +1,32 @@ +name: Build and Push Docker image + +on: + push: + branches: + - main + +jobs: + build-and-push: + runs-on: ubuntu-latest + + steps: + - name: Check out the repo + uses: actions/checkout@v2 + + - name: Log in to StuVe Gitea Container Registry + uses: docker/login-action@v1 + with: + registry: git.stuve.uni-ulm.de + username: ${{ secrets.USER_NAME }} + password: ${{ secrets.USER_PASSWORD }} + + - name: Build and push Docker image + uses: docker/build-push-action@v2 + with: + context: . + file: ./Dockerfile + push: true + tags: git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend:latest + + #- name: Trigger webhook + # run: curl -X POST ${{ secrets.WEBHOOK_URL }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index fd94a56..bd6948c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ pb_data # env *.env.local +*.env # macOS .DS_Store \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 4144cb4..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,15 +0,0 @@ -stages: - - build - -build-container: - stage: build - image: - name: gcr.io/kaniko-project/executor:debug - entrypoint: [ "" ] - script: - - /kaniko/executor - --context "${CI_PROJECT_DIR}" - --dockerfile "${CI_PROJECT_DIR}/Dockerfile" - --destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG}" - only: - - main \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 0a2722b..862670f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,10 @@ # This is the first stage of a multi-stage build. FROM golang:1.19 as builder +# Set labels +LABEL maintainer="Valentin Kolb" +LABEL version="1.0" + # Set the working directory inside the container. WORKDIR /build diff --git a/Readme.md b/Readme.md index 6443014..f2f9d54 100644 --- a/Readme.md +++ b/Readme.md @@ -3,7 +3,7 @@ ## Übersicht Als Backend für die StuVe IT Tools wird [Pocketbase](https://pocketbase.io) -verwendet. Pocketbase ist ein Backend-as-a-Service, der auf SQLite basiert. +verwendet. Pocketbase ist ein self-hosted Backend-as-a-Service, der auf SQLite basiert. ## Was ist Pocketbase? @@ -54,9 +54,9 @@ Funktionen für die einzelnen Tabellen und User erlaubt sind. ## Entwickeln ```bash -git clone ... -cd backend -go run main.go serve # --debug=0 for no debug output +git clone https://git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend.git +cd stuve-it-backend +go run main/main.go serve # --debug=0 for no debug output ``` ## Installation @@ -84,8 +84,10 @@ Die einzelnen custom go Packages sind weiter unten beschrieben. │ ├── models.go # Structs für LDAP User und Group │ ├── tables.go # Erstellt die benötigten Tabellen in der Datenbank (Users, Groups, Logs) │ └── ldapSync.go # Sync Funktionen (Sync Users, Groups, ...) -└── ldapLogin # LDAP Login Package - └── main.go # Login Funktionen (Login Route) +├── ldapLogin # LDAP Login Package +│ └── main.go # Login Funktionen (Login Route) +└── qrApi # QR API Package + └── main.go # Api Route ``` ## Custom Packages @@ -98,7 +100,46 @@ wurden einige Packages erstellt, die Pocketbase erweitern. Das Logger Package ist ein custom Logger, der die Log-Nachrichten farblich in die Konsole schreibt. Diese Package ist sehr minimal gehalten und bietet nur die -Funktionen `Info`, `Warn` und `Error` an. +Funktionen `Success`, `Info` und `Error` an. + +#### Beispiel + +```go +package main + +import "git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/logger" + +func main() { + logger.LogInfoF("Info Message") + logger.LogErrorF("Warn Message") + logger.LogErrorF("Error Message") +} +``` + +### QR API + +Das QR API Package ist ein einfaches Package, das eine API Route +zum Generieren von QR-Codes in SVG anbietet. + +#### API Route + +Das Package bietet die Route `GET /api/qr/v1` an, welche die folgenden +Parameter als json entgegennimmt: + +| Parameter | Datentyp | Beschreibung | +|-------------------|-------------|------------------------------------------------------------------------------------------------| +| `data` | string | Der Text, der im QR-Code codiert werden soll (**required**) | +| `ecc` | string-enum | Der Error Correction Level des QR-Codes (`L`, `M` - default, `Q`, `H`) | +| `scale` | int | Der Scale des QR-Codes (default `1`) | +| `border` | int | Der Border des QR-Codes (default `1`), eine Border Uni ist die Größe von einem QR-Code 'pixel' | +| `color` | hex-string | Die Farbe des QR-Codes (default `#000000`) | +| `colorBackground` | hex-string | Die Hintergrundfarbe des QR-Codes (default `#FFFFFF`) | + +#### Beispiel + +```bash +curl -X GET https://it.stuve.uni-ulm.de/api/qr/v1 -d '{"data": "https://stuve.uni-ulm.de"}' +``` ### LDAP Sync @@ -108,6 +149,36 @@ die LDAP Datenbank mit der Pocketbase Datenbank. Dabei werden alle LDAP User und Gruppen in die Pocketbase Datenbank kopiert. +Dieses Package erfordert, dass alle Gruppen und User das Attribut `objectGUID` haben. Dies ist in der Regel bei Windows +ADs der Fall. + +#### LDAP Attribute + +Die folgenden LDAP Attribute werden in der Pocketbase Datenbank gespeichert. + +##### User + +| Attribut | Pocketbase Name | Beschreibung | +|----------------|--------------------------------------------|-------------------------------------------------------------------| +| objectGUID | id | Eindeutige ID des Users (nur die ersten 15 Zeichen werden genutzt | +| givenName | givenName | Vorname des Users | +| sn | sn | Nachname des Users | +| mail | email | E-Mail des Users | +| memberOf | memberOf (in Pocketbase eine SQL Relation) | Gruppenzugehörigkeit des Users | +| accountExpires | accountExpires (in Pocketbase UTC) | Account Ablaufdatum | +| dn | dn | Distinguished Name des Users | +| cn | cn & username | Common Name des Users | + +##### Group + +| Attribut | Pocketbase Name | Beschreibung | +|-------------|--------------------------------------------|--------------------------------------------------------------------| +| objectGUID | id | Eindeutige ID der Gruppe (nur die ersten 15 Zeichen werden genutzt | +| cn | cn | Common Name der Gruppe | +| dn | dn | Distinguished Name der Gruppe | +| description | description | Beschreibung der Gruppe | +| memberOf | memberOf (in Pocketbase eine SQL Relation) | Gruppenzugehörigkeit der Gruppe | + #### Konfiguration Das Package benötigt folgende ENV Variablen: @@ -147,7 +218,7 @@ noch nicht existiert. In diesem Fall wird die Beziehung erst beim nächsten Sync erstellt. Falls eine `memberOf` Gruppe nicht gefunden wird, wird dies in der Konsole geloggt. -Für jede Synchronisation wird ein Log in der `ldap_sync_logs` Tabelle +Für jede Synchronisation wird ein Log-in der `ldap_sync_logs` Tabelle erstellt. Sowohl für die ldap_users als auch für die ldap_groups Tabelle wird diff --git a/go.mod b/go.mod index f372ec5..2a53a81 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,14 @@ -module gitlab.uni-ulm.de/stuve-it/it-tools/backend +module git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend go 1.21 require ( + github.com/fatih/color v1.15.0 github.com/go-ldap/ldap/v3 v3.4.6 + github.com/google/uuid v1.3.1 + github.com/joho/godotenv v1.5.1 + github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 + github.com/pocketbase/dbx v1.10.1 github.com/pocketbase/pocketbase v0.19.0 ) @@ -34,7 +39,6 @@ require ( github.com/disintegration/imaging v1.6.2 // indirect github.com/domodwyer/mailyak/v3 v3.6.2 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/fatih/color v1.15.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/ganigeorgiev/fexpr v0.3.0 // indirect github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect @@ -43,23 +47,20 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/uuid v1.3.1 // indirect github.com/google/wire v0.5.0 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/joho/godotenv v1.5.1 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-sqlite3 v1.14.17 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect - github.com/pocketbase/dbx v1.10.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.8.4 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect go.opencensus.io v0.24.0 // indirect diff --git a/go.sum b/go.sum index 0226b58..3b1450a 100644 --- a/go.sum +++ b/go.sum @@ -207,8 +207,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= diff --git a/ldapLogin/main.go b/ldapLogin/main.go index aae1c4d..ed89bb0 100644 --- a/ldapLogin/main.go +++ b/ldapLogin/main.go @@ -2,13 +2,13 @@ package ldapLogin import ( "fmt" + "git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/ldapSync" + "git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/logger" "github.com/go-ldap/ldap/v3" "github.com/labstack/echo/v5" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" - "gitlab.uni-ulm.de/stuve-it/it-tools/backend/ldapSync" - "gitlab.uni-ulm.de/stuve-it/it-tools/backend/logger" "os" ) diff --git a/ldapSync/db.go b/ldapSync/db.go index b63a701..062cc12 100644 --- a/ldapSync/db.go +++ b/ldapSync/db.go @@ -6,11 +6,11 @@ this file contains functions to sync ldap users and groups to the database import ( "fmt" + "git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/logger" "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/forms" "github.com/pocketbase/pocketbase/models" - "gitlab.uni-ulm.de/stuve-it/it-tools/backend/logger" ) // upsertLDAPGroup This function creates / updates a record in the ldap groups table @@ -27,8 +27,8 @@ func upsertLDAPGroup(app *pocketbase.PocketBase, ldapGroup *LDAPGroup) error { // if record exists, update it if res, _ := app.Dao().FindFirstRecordByFilter( ldapGroupsTableName, - "gidNumber = {:gidNumber}", - dbx.Params{"gidNumber": ldapGroup.gidNumber}, + "id = {:id}", + dbx.Params{"id": ldapGroup.id}, ); res != nil { record = res } else { // if record does not exist, create it @@ -51,7 +51,7 @@ func upsertLDAPGroup(app *pocketbase.PocketBase, ldapGroup *LDAPGroup) error { // load data err = form.LoadData(map[string]any{ - "gidNumber": ldapGroup.gidNumber, + "id": ldapGroup.id, "cn": ldapGroup.cn, "dn": ldapGroup.dn, "description": ldapGroup.description, @@ -83,8 +83,8 @@ func upsertLDAPUser(app *pocketbase.PocketBase, ldapUser *LDAPUser) error { // if record exists, update it if res, _ := app.Dao().FindFirstRecordByFilter( ldapUsersTableName, - "uidNumber = {:uidNumber}", - dbx.Params{"uidNumber": ldapUser.uidNumber}, + "id = {:id}", + dbx.Params{"id": ldapUser.id}, ); res != nil { record = res } else { // if record does not exist, create it @@ -111,7 +111,7 @@ func upsertLDAPUser(app *pocketbase.PocketBase, ldapUser *LDAPUser) error { } } - record.Set("uidNumber", ldapUser.uidNumber) + record.Set("id", ldapUser.id) record.Set("givenName", ldapUser.givenName) record.Set("sn", ldapUser.sn) record.Set("username", ldapUser.cn) diff --git a/ldapSync/ldapSync.go b/ldapSync/ldapSync.go index f89772e..0de06da 100644 --- a/ldapSync/ldapSync.go +++ b/ldapSync/ldapSync.go @@ -3,12 +3,13 @@ package ldapSync import ( "encoding/json" "fmt" + "git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/logger" "github.com/go-ldap/ldap/v3" + "github.com/google/uuid" "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/forms" "github.com/pocketbase/pocketbase/models" - "gitlab.uni-ulm.de/stuve-it/it-tools/backend/logger" "os" "time" ) @@ -20,6 +21,21 @@ type SyncResult struct { Errors []error } +// getObjectGUID This function gets the objectGUID from an ldap entry +// +// since the objectGUID is a binary value, it is returned as a string (uuid) +func getObjectGUID(entry *ldap.Entry) (string, error) { + // get the objectGUID + var bytes = entry.GetRawAttributeValue("objectGUID") + + // convert to uuid + u, err := uuid.FromBytes(bytes) + if err != nil { + return "", err + } + return u.String()[:15], nil // only use the first 15 characters of the uuid +} + // upsertLDAPGroup This function creates / updates a record in the ldap groups table // // this function expects that the table ldapGroups already exists @@ -34,7 +50,7 @@ func syncLdapGroups(app *pocketbase.PocketBase, ldapClient *ldap.Conn) SyncResul os.Getenv("LDAP_GROUP_BASE_DN"), // The base dn to search ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, os.Getenv("LDAP_GROUP_FILTER"), // The filter to apply - []string{"gidNumber", "description", "dn", "cn", "msSFU30NisDomain", "memberOf"}, + []string{"description", "dn", "cn", "msSFU30NisDomain", "memberOf", "objectGUID"}, nil, ) @@ -46,8 +62,15 @@ func syncLdapGroups(app *pocketbase.PocketBase, ldapClient *ldap.Conn) SyncResul } for _, entry := range groupsFoundInLdap.Entries { + + var id, e = getObjectGUID(entry) + + if e != nil { + errors = append(errors, fmt.Errorf("unable to get objectGUID for group with dn: %s - %s", entry.DN, e)) + } + err := upsertLDAPGroup(app, &LDAPGroup{ - gidNumber: entry.GetAttributeValue("gidNumber"), + id: id, description: entry.GetAttributeValue("description"), dn: entry.DN, cn: entry.GetAttributeValue("cn"), @@ -115,7 +138,7 @@ func syncLdapUsers(app *pocketbase.PocketBase, ldapClient *ldap.Conn) SyncResult os.Getenv("LDAP_BASE_DN"), // The base dn to search ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, os.Getenv("LDAP_USER_FILTER"), // The filter to apply - []string{"givenName", "sn", "accountExpires", "uidNumber", "mail", "dn", "cn", "msSFU30NisDomain", "memberOf"}, + []string{"givenName", "sn", "accountExpires", "mail", "dn", "cn", "msSFU30NisDomain", "memberOf", "objectGUID"}, nil, ) @@ -127,11 +150,18 @@ func syncLdapUsers(app *pocketbase.PocketBase, ldapClient *ldap.Conn) SyncResult } for _, entry := range usersFoundInLdap.Entries { + + var id, e = getObjectGUID(entry) + + if e != nil { + errors = append(errors, fmt.Errorf("unable to get objectGUID for user with dn: %s - %s", entry.DN, e)) + } + err := upsertLDAPUser(app, &LDAPUser{ givenName: entry.GetAttributeValue("givenName"), sn: entry.GetAttributeValue("sn"), accountExpires: entry.GetAttributeValue("accountExpires"), - uidNumber: entry.GetAttributeValue("uidNumber"), + id: id, mail: entry.GetAttributeValue("mail"), dn: entry.DN, cn: entry.GetAttributeValue("cn"), diff --git a/ldapSync/main.go b/ldapSync/main.go index 17b3279..2fafd44 100644 --- a/ldapSync/main.go +++ b/ldapSync/main.go @@ -5,9 +5,9 @@ package ldapSync import ( "fmt" + "git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/logger" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/tools/cron" - "gitlab.uni-ulm.de/stuve-it/it-tools/backend/logger" "os" "strconv" "time" @@ -88,7 +88,7 @@ func InitLdapSync(app *pocketbase.PocketBase) error { ldapSyncSchedule := os.Getenv("LDAP_SYNC_SCHEDULE") - // syncs ldap every 2 minutes + // syncs ldap - interval specified in the LDAP_SYNC_SCHEDULE env variable scheduler.MustAdd("ldapSync", ldapSyncSchedule, func() { logger.LogInfoF("syncing LDAP ...") syncLdap(app) diff --git a/ldapSync/models.go b/ldapSync/models.go index 597abfd..77c5d55 100644 --- a/ldapSync/models.go +++ b/ldapSync/models.go @@ -1,10 +1,10 @@ package ldapSync type LDAPUser struct { + id string givenName string sn string accountExpires string - uidNumber string mail string dn string @@ -14,7 +14,7 @@ type LDAPUser struct { } type LDAPGroup struct { - gidNumber string + id string description string dn string diff --git a/ldapSync/tables.go b/ldapSync/tables.go index 4d6bed5..e4ab007 100644 --- a/ldapSync/tables.go +++ b/ldapSync/tables.go @@ -33,13 +33,6 @@ func createLDAPGroupsTable(app *pocketbase.PocketBase) error { form.UpdateRule = nil form.DeleteRule = nil - // add group ID field - form.Schema.AddField(&schema.SchemaField{ - Name: "gidNumber", - Type: schema.FieldTypeText, - Required: true, - }) - // add description field form.Schema.AddField(&schema.SchemaField{ Name: "description", @@ -64,7 +57,7 @@ func createLDAPGroupsTable(app *pocketbase.PocketBase) error { // create index on cn form.Indexes = types.JsonArray[string]{ - "CREATE UNIQUE INDEX idx_ldapGroups ON " + ldapGroupsTableName + " (cn, gidNumber, dn)", + "CREATE UNIQUE INDEX idx_ldapGroups ON " + ldapGroupsTableName + " (cn, dn)", } // validate and submit (internally it calls app.Dao().SaveCollection(collection) in a transaction) @@ -144,13 +137,6 @@ func createLDAPUsersTable(app *pocketbase.PocketBase) error { Required: true, }) - // add uidNumber field - form.Schema.AddField(&schema.SchemaField{ - Name: "uidNumber", - Type: schema.FieldTypeText, - Required: true, - }) - // add surname field form.Schema.AddField(&schema.SchemaField{ Name: "sn", @@ -185,7 +171,7 @@ func createLDAPUsersTable(app *pocketbase.PocketBase) error { // create index on username form.Indexes = types.JsonArray[string]{ - "CREATE UNIQUE INDEX idx_ldapUsers ON " + ldapGroupsTableName + " (cn, uidNumber, dn)", + "CREATE UNIQUE INDEX idx_ldapUsers ON " + ldapGroupsTableName + " (cn, dn)", } return form.Submit() diff --git a/main/main.go b/main/main.go index 9d69687..31d6fe0 100644 --- a/main/main.go +++ b/main/main.go @@ -1,19 +1,27 @@ package main import ( + "git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/ldapLogin" + "git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/ldapSync" + "git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/logger" + "git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/qrApi" "github.com/joho/godotenv" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/core" - "gitlab.uni-ulm.de/stuve-it/it-tools/backend/ldapLogin" - "gitlab.uni-ulm.de/stuve-it/it-tools/backend/ldapSync" "log" ) func main() { // load env - godotenv.Load(".env.local") - godotenv.Load(".env") + err := godotenv.Load(".env.local") + if err != nil { + logger.LogInfoF("The file '.env.local' could not be loaded.") + } + err = godotenv.Load(".env") + if err != nil { + logger.LogInfoF("The file '.env' could not be loaded.") + } // create app app := pocketbase.New() @@ -28,6 +36,11 @@ func main() { return ldapLogin.InitLDAPLogin(app, e) }) + // setup qr api + app.OnBeforeServe().Add(func(e *core.ServeEvent) error { + return qrApi.InitQRApi(app, e) + }) + // start app if err := app.Start(); err != nil { log.Fatal(err) diff --git a/qrApi/main.go b/qrApi/main.go new file mode 100644 index 0000000..137f118 --- /dev/null +++ b/qrApi/main.go @@ -0,0 +1,111 @@ +package qrApi + +import ( + "bytes" + goqr "git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/go-qr" + "git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/logger" + "github.com/labstack/echo/v5" + "github.com/pocketbase/pocketbase" + "github.com/pocketbase/pocketbase/apis" + "github.com/pocketbase/pocketbase/core" + "io" +) + +// InitQRApi initializes ar code api endpoint +// +// this endpoint can be used to create qr codes +// GET /api/qr/v1 +// +// the endpoint expects the following request data: +// +// { +// "data": "what to be encoded", +// "ecc": "error correction level ('L', 'M' (default), 'Q', 'H')", +// "scale": "scale of qr code (default 1), must be greater than 0", +// "border": "border of qr code (default 1), must be equal or greater than 0, one is equal to one qr-code 'pixel'" +// "color": "color of qr code (default #000000)" +// "colorBackground": "background color of qr code (default #FFFFFF)", +// } +// +// if the user is authenticated successfully the endpoint returns and apis.RecordAuthResponse +func InitQRApi(app *pocketbase.PocketBase, e *core.ServeEvent) error { + + // add endpoint to app + logger.LogInfoF("Adding QR API Endpoint") + + e.Router.GET("/api/qr/v1", func(c echo.Context) error { + + // get data from request + data := struct { + Data string `json:"data"` + ECC string `json:"ecc"` + Scale int `json:"scale"` + Border int `json:"border"` + ColorDark string `json:"color"` + ColorLight string `json:"colorBackground"` + }{} + if err := c.Bind(&data); err != nil { + return apis.NewBadRequestError("Failed to read request data", err) + } + + // get correct error correction level + var errCorLvl goqr.Ecc + switch data.ECC { + case "L": + errCorLvl = goqr.Low + case "M": + errCorLvl = goqr.Medium + case "Q": + errCorLvl = goqr.Quartile + case "H": + errCorLvl = goqr.High + default: + errCorLvl = goqr.Medium + } + + // encode data to qr code + qrCode, err := goqr.EncodeText(data.Data, errCorLvl) + if err != nil { + return apis.NewBadRequestError("Failed to encode data to qr code", err) + } + + // set default scale and border + scale := data.Scale + border := data.Border + if scale == 0 { + scale = 1 + } + if border == 0 { + border = 1 + } + config := goqr.NewQrCodeImgConfig(scale, border) + + // create new buffer and writer + var buffer bytes.Buffer + writer := io.Writer(&buffer) + + // set default colors + colorLight := data.ColorLight + colorDark := data.ColorDark + if colorLight == "" { + colorLight = "#FFFFFF" + } + if colorDark == "" { + colorDark = "#000000" + } + + // write qr code to buffer + err = qrCode.WriteAsSVG(config, writer, colorLight, colorDark) + + // return error if writing qr code to buffer fails + if err != nil { + logger.LogErrorF("Failed to write QR Code: %e", err) + return apis.NewBadRequestError("Failed to write QR Code", err) + } + + // return qr code as svg + return c.Stream(200, "image/svg+xml", io.Reader(&buffer)) + }, apis.ActivityLogger(app)) + + return nil +}