Skip to content

Commit 1c04883

Browse files
committed
init OPP auth
1 parent d176061 commit 1c04883

File tree

15 files changed

+891
-15
lines changed

15 files changed

+891
-15
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
TODO

Containerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ RUN go build -o /go/bin/opp-auth
1414
FROM alpine:latest AS production
1515
RUN apk --no-cache add ca-certificates tzdata
1616
WORKDIR /root/
17-
COPY --from=0 /go/bin/opp-auth .
18-
COPY --from=0 /go/src/app/api/openapi.yaml ./api/openapi.yaml
17+
COPY --from=builder /go/bin/opp-auth .
18+
COPY --from=builder /go/src/app/api/openapi.yaml ./api/openapi.yaml
1919
EXPOSE 8080
2020

2121
CMD ["./opp-auth"]

Containerfile.dev

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
FROM golang:1.22-alpine AS builder
2-
RUN apk add --no-cache git make gcc musl-dev curl
2+
RUN apk add --no-cache git make gcc musl-dev curl openssl
33
WORKDIR /src
44
RUN go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest
55
ENTRYPOINT ["/src/entrypoint.sh"]

compose.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,19 @@ services:
99
- .:/src/
1010
ports:
1111
- "8080:8080"
12+
depends_on:
13+
- postgres
14+
postgres:
15+
image: postgres:latest
16+
environment:
17+
POSTGRES_USER: user
18+
POSTGRES_PASSWORD: password
19+
POSTGRES_DB: db
20+
ports:
21+
- "5432:5432"
22+
volumes:
23+
- postgres_data:/var/lib/postgresql/data
24+
25+
volumes:
26+
postgres_data:
27+
driver: local

entrypoint.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,11 @@ go generate && echo "API generated" || echo "API generation failed"
1616
echo "Building app..."
1717
go build -buildvcs=false -o /go/bin/opp-auth .
1818

19+
mkdir -p /root/keys && \
20+
openssl genrsa -out /root/keys/private.pem 4096 && \
21+
openssl rsa -in /root/keys/private.pem -pubout -out /root/keys/public.pem
22+
export PRIVATE_KEY=$(cat /root/keys/private.pem | base64 -w 0)
23+
export PUBLIC_KEY=$(cat /root/keys/public.pem | base64 -w 0)
24+
1925
echo "Starting app..."
2026
/go/bin/opp-auth

src/auth.go

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,52 @@
1-
//go:generate oapi-codegen -include-tags=session -generate types,gin-server -o api/api.gen.go -package api api/openapi.yaml
1+
//go:generate oapi-codegen -include-tags=session,user -generate types,gin-server -o api/api.gen.go -package api api/openapi.yaml
22

33
package main
44

55
import (
66
"OPP/auth/api"
7+
"OPP/auth/db"
78
"OPP/auth/handlers"
9+
"OPP/auth/jwt"
10+
"OPP/auth/rbac"
11+
"context"
812
"fmt"
913
"log"
1014
"os"
1115

16+
"github.com/getkin/kin-openapi/openapi3filter"
1217
"github.com/gin-gonic/gin"
1318
ginmiddleware "github.com/oapi-codegen/gin-middleware"
1419
"github.com/oapi-codegen/oapi-codegen/v2/pkg/util"
1520
)
1621

1722
var DEBUG_MODE = os.Getenv("DEBUG_MODE") == "true"
1823

24+
type opp_handlers struct {
25+
handlers.SessionHandlers
26+
handlers.UserHandlers
27+
}
28+
1929
func main() {
2030

31+
rbac := rbac.SetupRBAC()
32+
if rbac == nil {
33+
log.Panicf("Failed to setup RBAC")
34+
}
35+
36+
if err := db.Init(); err != nil {
37+
log.Panicf("Failed to initialize database: %v", err)
38+
}
39+
if db.GetDB() == nil {
40+
log.Panicf("Failed to get database instance")
41+
} else {
42+
defer db.GetDB().Close()
43+
}
44+
45+
opp_auth_handlers := &opp_handlers{
46+
SessionHandlers: *handlers.NewSessionHandler(),
47+
UserHandlers: *handlers.NewUserHandler(),
48+
}
49+
2150
r := gin.New()
2251
r.Use(gin.Logger())
2352
r.Use(gin.Recovery())
@@ -40,8 +69,14 @@ func main() {
4069

4170
// Set up the authentication function
4271
validatorOptions := &ginmiddleware.Options{
72+
Options: openapi3filter.Options{
73+
AuthenticationFunc: func(ctx context.Context, input *openapi3filter.AuthenticationInput) error {
74+
return jwt.AuthenticationFunc(ctx, input)
75+
},
76+
},
4377
SilenceServersWarning: silenceServersWarning,
4478
}
79+
4580
validator := ginmiddleware.OapiRequestValidatorWithOptions(spec, validatorOptions)
4681
if err != nil {
4782
log.Panicf("Failed to create validator: %v", err)
@@ -55,8 +90,7 @@ func main() {
5590
ErrorHandler: nil,
5691
}
5792

58-
SessionHandler := handlers.NewSessionHandler()
59-
api.RegisterHandlersWithOptions(r, SessionHandler, options)
93+
api.RegisterHandlersWithOptions(r, opp_auth_handlers, options)
6094

6195
fmt.Println("OPP Backend starting on :8080")
6296
log.Fatal(r.Run(":8080"))

src/dao/user.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package dao
2+
3+
import (
4+
"OPP/auth/api"
5+
"OPP/auth/db"
6+
"context"
7+
"errors"
8+
"fmt"
9+
"strings"
10+
)
11+
12+
var (
13+
ErrUserAlreadyExists = errors.New("user already exists")
14+
ErrInvalidUser = errors.New("invalid user data")
15+
ErrUserNotFound = errors.New("user not found")
16+
ErrInvalidPassword = errors.New("invalid password")
17+
)
18+
19+
type UserDao struct {
20+
db db.DB
21+
}
22+
23+
func NewUserDao() *UserDao {
24+
return &UserDao{
25+
db: *db.GetDB(),
26+
}
27+
}
28+
29+
func (d *UserDao) GetUsers(c context.Context, limit *int, offset *int) []api.UserResponse {
30+
query := "SELECT username, name, surname, email FROM users LIMIT $1 OFFSET $2"
31+
params := []any{20, 0}
32+
if limit != nil {
33+
params[0] = *limit
34+
}
35+
if offset != nil {
36+
params[1] = *offset
37+
}
38+
39+
var users []api.UserResponse
40+
rows, err := d.db.Query(c, query, params...)
41+
if err != nil {
42+
fmt.Printf("db error: %v\n", err.Error())
43+
return users
44+
}
45+
defer rows.Close()
46+
47+
for rows.Next() {
48+
var user api.UserResponse
49+
if err := rows.Scan(&user.Username, &user.Name, &user.Surname, &user.Email); err != nil {
50+
fmt.Printf("row scan error: %v\n", err.Error())
51+
continue
52+
}
53+
users = append(users, user)
54+
}
55+
return users
56+
}
57+
58+
func (d *UserDao) AddUser(c context.Context, user api.UserRequest) error {
59+
query := "INSERT INTO users (username, name, surname, email, password, role) VALUES ($1, $2, $3, $4, $5, $6)"
60+
_, err := d.db.Exec(c, query, user.Username, user.Name, user.Surname, user.Email, user.Password, user.Role)
61+
if err != nil {
62+
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
63+
return ErrUserAlreadyExists
64+
}
65+
return fmt.Errorf("failed to add user: %w", err)
66+
}
67+
return nil
68+
}
69+
70+
func (d *UserDao) GetUser(c context.Context, username string) (*api.UserResponse, error) {
71+
query := "SELECT username, name, surname, email FROM users WHERE username = $1"
72+
rows, err := d.db.Query(c, query, username)
73+
if err != nil {
74+
return nil, fmt.Errorf("db error: %w", err)
75+
}
76+
defer rows.Close()
77+
78+
var user api.UserResponse
79+
if rows.Next() {
80+
if err := rows.Scan(&user.Username, &user.Name, &user.Surname, &user.Email); err != nil {
81+
return nil, fmt.Errorf("failed to scan user: %w", err)
82+
}
83+
return &user, nil
84+
}
85+
return nil, ErrUserNotFound
86+
}
87+
88+
func (d *UserDao) GetUserRole(c context.Context, username string) (api.UserRequestRole, error) {
89+
query := "SELECT role FROM users WHERE username = $1"
90+
rows, err := d.db.Query(c, query, username)
91+
if err != nil {
92+
return "", fmt.Errorf("db error: %w", err)
93+
}
94+
defer rows.Close()
95+
96+
var role api.UserRequestRole
97+
if rows.Next() {
98+
if err := rows.Scan(&role); err != nil {
99+
return "", fmt.Errorf("failed to scan user role: %w", err)
100+
}
101+
return role, nil
102+
}
103+
return "", ErrUserNotFound
104+
}
105+
106+
func (d *UserDao) CheckPassword(c context.Context, username string, password string) error {
107+
query := "SELECT password FROM users WHERE username = $1"
108+
rows, err := d.db.Query(c, query, username)
109+
if err != nil {
110+
return fmt.Errorf("db error: %w", err)
111+
}
112+
defer rows.Close()
113+
114+
var storedPassword string
115+
if rows.Next() {
116+
if err := rows.Scan(&storedPassword); err != nil {
117+
return fmt.Errorf("failed to scan password: %w", err)
118+
}
119+
if storedPassword != password {
120+
return ErrInvalidPassword
121+
}
122+
return nil
123+
}
124+
return ErrUserNotFound
125+
}
126+
127+
func (d *UserDao) DeleteAllUsers(c context.Context) error {
128+
query := "DELETE FROM users"
129+
_, err := d.db.Exec(c, query)
130+
if err != nil {
131+
return fmt.Errorf("failed to delete all users: %w", err)
132+
}
133+
return nil
134+
}
135+
136+
func (d *UserDao) DeleteUser(c context.Context, username string) error {
137+
query := "DELETE FROM users WHERE username = $1"
138+
result, err := d.db.Exec(c, query, username)
139+
if err != nil {
140+
return fmt.Errorf("failed to delete user: %w", err)
141+
}
142+
143+
rowsAffected := result.RowsAffected()
144+
if rowsAffected == 0 {
145+
return ErrUserNotFound
146+
}
147+
return nil
148+
}
149+
150+
func (d *UserDao) UpdateUser(c context.Context, username string, user api.UserRequest) error {
151+
query := "UPDATE users SET name = $1, surname = $2, email = $3 WHERE username = $4"
152+
result, err := d.db.Exec(c, query, user.Name, user.Surname, user.Email, username)
153+
if err != nil {
154+
if strings.Contains(err.Error(), "UNIQUE constraint failed") {
155+
return ErrUserAlreadyExists
156+
}
157+
return fmt.Errorf("failed to update user: %w", err)
158+
}
159+
160+
rowsAffected := result.RowsAffected()
161+
if rowsAffected == 0 {
162+
return ErrUserNotFound
163+
}
164+
return nil
165+
}

src/db/db.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package db
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"sync"
8+
"time"
9+
10+
"github.com/jackc/pgx/v5"
11+
"github.com/jackc/pgx/v5/pgconn"
12+
"github.com/jackc/pgx/v5/pgxpool"
13+
)
14+
15+
type DB struct {
16+
pool *pgxpool.Pool
17+
}
18+
19+
// Singleton instance of DB
20+
var instance *DB
21+
var once sync.Once
22+
var initErr error
23+
24+
var schemaPath = "postegres_schema_v1.sql"
25+
26+
func Init() error {
27+
once.Do(func() {
28+
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
29+
defer cancel()
30+
31+
pool, err := pgxpool.New(ctx, "postgres://user:password@172.17.0.1:5432/db")
32+
if err != nil {
33+
initErr = fmt.Errorf("unable to create connection pool: %w", err)
34+
return
35+
}
36+
if err := pool.Ping(ctx); err != nil {
37+
pool.Close()
38+
initErr = fmt.Errorf("database ping failed: %w", err)
39+
return
40+
}
41+
42+
// Read schema file
43+
schemaSQL, err := os.ReadFile("db/" + schemaPath)
44+
if err != nil {
45+
pool.Close()
46+
initErr = fmt.Errorf("failed to read schema file: %w", err)
47+
return
48+
}
49+
// Apply schema
50+
_, err = pool.Exec(ctx, string(schemaSQL))
51+
if err != nil {
52+
pool.Close()
53+
initErr = fmt.Errorf("failed to apply database schema: %w", err)
54+
return
55+
}
56+
57+
instance = &DB{pool: pool}
58+
fmt.Println("Database connection pool created successfully")
59+
})
60+
61+
return initErr
62+
}
63+
64+
func GetDB() *DB {
65+
return instance
66+
}
67+
68+
func (d *DB) Close() {
69+
if d.pool != nil {
70+
d.pool.Close()
71+
d.pool = nil
72+
}
73+
}
74+
75+
func (d *DB) Query(ctx context.Context, query string, args ...any) (pgx.Rows, error) {
76+
if d.pool == nil {
77+
return nil, pgx.ErrTxClosed
78+
}
79+
return d.pool.Query(ctx, query, args...)
80+
}
81+
82+
func (d *DB) QueryRow(ctx context.Context, query string, args ...any) pgx.Row {
83+
if d.pool == nil {
84+
return nil
85+
}
86+
return d.pool.QueryRow(ctx, query, args...)
87+
}
88+
89+
func (d *DB) Exec(ctx context.Context, query string, args ...any) (pgconn.CommandTag, error) {
90+
if d.pool == nil {
91+
return pgconn.CommandTag{}, pgx.ErrTxClosed
92+
}
93+
return d.pool.Exec(ctx, query, args...)
94+
}

0 commit comments

Comments
 (0)