Skip to content

Commit 88e6801

Browse files
authored
Merge pull request #77 from teknologi-umum/fix/qr-ticketing
fix: qr ticketing sending and verification
2 parents 90e05db + 4222401 commit 88e6801

File tree

8 files changed

+115
-18
lines changed

8 files changed

+115
-18
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
--health-timeout 5s
3131
--health-retries 5
3232
smtp:
33-
image: mailhog/mailhog
33+
image: marlonb/mailcrab:latest
3434
ports:
3535
- 1025:1025
3636
steps:

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,4 +410,5 @@ cython_debug/
410410

411411

412412
./backend/*.sh
413-
!./backend/blast-email.sh
413+
!./backend/blast-email.sh
414+
./backend/configuration.yml

backend/README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Backend
2+
3+
## Developing
4+
5+
To develop the backend, just create integration test and make sure it's running on CI.
6+
7+
But, if you want to run it locally, copy the `configuration.example.yml` into `configuration.yml`,
8+
then start the Docker Compose file on the root directory using `docker compose up -d postgres mailcrab`.
9+
10+
Your `configuration.yml` file should be similar to:
11+
12+
```yaml
13+
feature_flags:
14+
registration_closed: false
15+
16+
environment: local
17+
18+
database:
19+
host: localhost
20+
port: 5432
21+
user: conference
22+
password: VeryStrongPassword
23+
database: conference
24+
25+
port: 8080
26+
27+
mailer:
28+
hostname: localhost
29+
port: 1025
30+
from: administrator@localhost
31+
password:
32+
33+
blob_url: file:///tmp/teknologi-umum-conference
34+
35+
signature:
36+
public_key: 2bb6b9b9e1d9e12bfdd4196bfba6a081156ac...
37+
private_key: 48d0ca64011fec1cb23b21820e9f7e880843e71f236b7f8decfe3568f...
38+
39+
validate_payment_key: 24326124313024514d56324d782f4a7342446f36363653784b324175657341...
40+
```
41+
42+
Generate the `signature.public_key` and `signature.private_key` using this simple Go script:
43+
44+
```go
45+
package main
46+
47+
import (
48+
"crypto/ed25519"
49+
"encoding/hex"
50+
"fmt"
51+
)
52+
53+
func main() {
54+
pub, priv, _ := ed25519.GenerateKey(nil)
55+
fmt.Println(hex.EncodeToString(pub))
56+
fmt.Println(hex.EncodeToString(priv))
57+
}
58+
```
59+
60+
Generate the `validate_payment_key` using this simple Go script:
61+
62+
```go
63+
package main
64+
65+
import (
66+
"fmt"
67+
68+
"golang.org/x/crypto/bcrypt"
69+
)
70+
71+
func main() {
72+
passphrase := "test"
73+
value, _ := bcrypt.GenerateFromPassword([]byte(passphrase), 10)
74+
fmt.Printf("%x", value)
75+
76+
}
77+
```

backend/mailer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func (m *Mailer) messageBuilder(ctx context.Context, mail *Mail) []byte {
8888
msg.WriteString("Content-Transfer-Encoding: 8bit\n")
8989
msg.WriteString("\n")
9090
msg.WriteString(mail.PlainTextBody)
91-
msg.WriteString("\n")
91+
msg.WriteString("\n\n")
9292
msg.WriteString("--" + alternateBoundary + "\n")
9393
msg.WriteString("Content-Type: text/html; charset=\"utf-8\"\n")
9494
msg.WriteString("Content-Transfer-Encoding: 8bit\n")

backend/server.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"encoding/hex"
45
"errors"
56
"mime"
67
"net/http"
@@ -248,7 +249,17 @@ func (s *ServerDependency) DayTicketScan(c echo.Context) error {
248249
}
249250

250251
// Validate key
251-
if err := bcrypt.CompareHashAndPassword([]byte(s.validateTicketKey), []byte(requestBody.Key)); err != nil {
252+
decodedPassphrase, err := hex.DecodeString(s.validateTicketKey)
253+
if err != nil {
254+
sentryHub.CaptureException(err)
255+
return c.JSON(http.StatusInternalServerError, echo.Map{
256+
"message": "Internal server error",
257+
"errors": "Internal server error",
258+
"request_id": requestId,
259+
})
260+
}
261+
262+
if err := bcrypt.CompareHashAndPassword(decodedPassphrase, []byte(requestBody.Key)); err != nil {
252263
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
253264
return c.JSON(http.StatusForbidden, echo.Map{
254265
"message": "Wrong passphrase",

backend/ticketing.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ func (t *TicketDomain) VerifyTicket(ctx context.Context, payload []byte) (email
350350
return "", "", false, ErrInvalidTicket
351351
}
352352

353-
ticketId, err := uuid.FromBytes(rawTicketId)
353+
ticketId, err := uuid.ParseBytes(rawTicketId)
354354
if err != nil {
355355
return "", "", false, ErrInvalidTicket
356356
}
@@ -380,20 +380,20 @@ func (t *TicketDomain) VerifyTicket(ctx context.Context, payload []byte) (email
380380

381381
tx, err := conn.BeginTx(ctx, pgx.TxOptions{
382382
IsoLevel: pgx.RepeatableRead,
383-
AccessMode: pgx.ReadOnly,
383+
AccessMode: pgx.ReadWrite,
384384
})
385385
if err != nil {
386386
return "", "", false, fmt.Errorf("creating transaction: %w", err)
387387
}
388388

389-
err = tx.QueryRow(ctx, "SELECT email, student FROM ticketing WHERE id = $1", ticketId).Scan(&email, &student)
389+
err = tx.QueryRow(ctx, "SELECT email, student FROM ticketing WHERE id = $1 AND used = FALSE", ticketId).Scan(&email, &student)
390390
if err != nil {
391391
if e := tx.Rollback(ctx); e != nil {
392392
return "", "", false, fmt.Errorf("rolling back transaction: %w (%s)", e, err.Error())
393393
}
394394

395395
if errors.Is(err, pgx.ErrNoRows) {
396-
return "", "", false, ErrInvalidTicket
396+
return "", "", false, fmt.Errorf("%w (id not exists, or ticket has been used)", ErrInvalidTicket)
397397
}
398398

399399
return "", "", false, fmt.Errorf("acquiring data from table: %w", err)

docker-compose.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ services:
3939
options:
4040
max-size: 10M
4141

42-
mailhog:
43-
image: mailhog/mailhog:latest
42+
mailcrab:
43+
image: marlonb/mailcrab:latest
4444
ports:
4545
- 127.0.0.1:1025:1025
46-
- 127.0.0.1:8025:8025
46+
- 127.0.0.1:8025:1080
4747
deploy:
4848
mode: replicated
4949
replicas: 1
@@ -94,7 +94,7 @@ services:
9494
condition: service_completed_successfully
9595
postgres:
9696
condition: service_healthy
97-
mailhog:
97+
mailcrab:
9898
condition: service_started
9999
logging:
100100
driver: local

frontend/pages/verify.vue

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,15 @@ const key = ref([])
1010
const config = useRuntimeConfig();
1111
const alertClass = ref<null|boolean>(null);
1212
const paused = ref(false)
13+
const invalidTicketReason = ref<string>("");
14+
interface ScanResponse {
15+
message: string
16+
student: boolean
17+
name: string
18+
email: string
19+
}
1320
const onDetect = async (a: any) => {
14-
15-
const response = await useFetch(`${config.public.backendBaseUrl}/scan-tiket`, {
21+
const response = await useFetch<ScanResponse>(`${config.public.backendBaseUrl}/scan-tiket`, {
1622
method: "POST",
1723
body: {
1824
code: a[0].rawValue,
@@ -23,12 +29,14 @@ const onDetect = async (a: any) => {
2329
2430
if (response.error.value?.statusCode && [406, 403].includes(response.error.value?.statusCode)) {
2531
alertClass.value = false
32+
invalidTicketReason.value = response.error.value?.data.errors;
2633
} else {
34+
const body = response.data.value;
2735
alertClass.value = true
2836
detectedUser.value = {
29-
name: 'Aji',
30-
student: true,
31-
institution: "PT Mencari Cinta Sejati"
37+
name: body?.name,
38+
student: body?.student,
39+
email: body?.email,
3240
}
3341
}
3442
setTimeout(() => {
@@ -45,7 +53,7 @@ const scanNext = () => {
4553
<div id="page">
4654
<SinglePage title="Verify Guest">
4755
<div :class="[`alert mb-5`, alertClass ? 'alert-success' : 'alert-danger']" v-if="alertClass !== null">
48-
{{ alertClass ? 'User verified!' : 'Invalid ticket' }}
56+
{{ alertClass ? 'User verified!' : invalidTicketReason }}
4957
</div>
5058
<template v-if="!detectedUser">
5159
<input type="text" class="form-control-lg mb-5" placeholder="Key" v-model="key">

0 commit comments

Comments
 (0)