Тестовое задание - см task.pdf Ветка для реализации дополнительных вопросов - development
Код проверялся на Fedora 39 Linux, возможно он будет работать и на других дистрибутивах. Требования
- GNU Make
dnf install make
- Golang
dnf install golang
- Podman -
dnf install podman podman-compose podman-plugins containernetworking-plugins
- Curl
dnf install curl
- Goose - https://github.com/pressly/goose - инструмент для миграций базы данных.
Теоритически podman можно заменить docker
+ docker-compose-plugin
но такой подход не
рекомендуется для моего дистрибутива.
- Убедитесь, что установлен goose
vodolaz095@steel:~/projects/asset_storage$ which goose
~/bin/goose
vodolaz095@steel:~/projects/asset_storage$ goose
Usage: goose [OPTIONS] DRIVER DBSTRING COMMAND
- Запустите базу данных PostgreSQL в контейнере (или докером, или подманом, а может быть, и с хоста)
make podman/resource
make docker/resource
- Убедитесь, что
goose
может соединиться с базой данных. Возможно, придётся отредактировать строку соединения в migrate.mk если база данных Postgres запущена по другому...
$ make migrate/info
goose --dir ./migrations/ postgres "user=assets password=assets dbname=assets host=localhost port=5432 sslmode=disable" status
2024/11/18 21:52:08 Applied At Migration
2024/11/18 21:52:08 =======================================
2024/11/18 21:52:08 Pending -- 00001_users.sql
2024/11/18 21:52:08 Pending -- 00002_sessions.sql
2024/11/18 21:52:08 Pending -- 00003_assets.sql
2024/11/18 21:52:08 Pending -- 00004_test_user.sql
- Примените миграции к базе данных - запустите
make migrate/up
4 раза
$ make migrate/up
goose --dir ./migrations/ postgres "user=assets password=assets dbname=assets host=localhost port=5432 sslmode=disable" up
2024/11/18 21:53:52 OK 00001_users.sql
2024/11/18 21:53:52 OK 00002_sessions.sql
2024/11/18 21:53:52 OK 00003_assets.sql
2024/11/18 21:53:52 OK 00004_test_user.sql
2024/11/18 21:53:52 goose: no migrations to run. current version: 4
Если вызвать make migrate/info
то покажет такое
$ make migrate/info
goose --dir ./migrations/ postgres "user=assets password=assets dbname=assets host=localhost port=5432 sslmode=disable" status
2024/11/18 21:53:58 Applied At Migration
2024/11/18 21:53:58 =======================================
2024/11/18 21:53:58 Mon Nov 18 18:53:52 2024 -- 00001_users.sql
2024/11/18 21:53:58 Mon Nov 18 18:53:52 2024 -- 00002_sessions.sql
2024/11/18 21:53:58 Mon Nov 18 18:53:52 2024 -- 00003_assets.sql
2024/11/18 21:53:58 Mon Nov 18 18:53:52 2024 -- 00004_test_user.sql
- Убедитесь, что в базе данных есть 4 таблицы
assets
goose_db_version
sessions
users
- Убедитесь, что в таблице
users
есть тестовый пользовательalice
/secret
. Запрос
select * from users;
Возвращает как минимум одного пользователя, у которого будет
login=alice
password_hash=5ebe2294ecd0e0f08eab7690d2a6ee69
- База данных готова!
Убедитесь, что версия компилятора не ниже 1.22.8 (на других не проверял)
vodolaz095@steel:~/projects/asset_storage$ go version
go version go1.22.8 linux/amd64
Запустите приложение: make start
или go run main.go
.
Конфигурацию можно задать через переменные окружения POSIX - см. config.go
По умолчанию приложение соединяется с базой данных на хост машине и включает HTTP сервер на http://localhost:3000
Приложение пишет логи в STDOUT и они должны быть видны на экране.
Возможно, придётся отредактировать docker-compose.yml
$ make podman/up
$ make docker/up
По умолчанию приложение соединяется с базой данных на в контейнере и включает HTTP сервер на 3000 порту хост машины, то есть, на http://localhost:3000. Приложение пишет логи в STDOUT и их можно увидеть стандартными средствами docker/podman.
Приложение тестируется в полуавтоматическом режиме путём отправки HTTP запросов на http://localhost:3000 с помощью curl. Вызовы curl можно упростить, используя рецепты из integration.mk.
Примерный ход тестирования приложения
- Успешно создаём сессию как пользователь
alice
с паролемsecret
$ make integration/auth_ok
curl -v --data '{"login":"alice","password":"secret"}' http://localhost:3000/api/auth
* processing: http://localhost:3000/api/auth
* Trying [::1]:3000...
* Connected to localhost (::1) port 3000
> POST /api/auth HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.2.1
> Accept: */*
> Content-Length: 37
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 200 OK
< Date: Mon, 18 Nov 2024 18:36:23 GMT
< Content-Length: 44
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host localhost left intact
{"token":"d3074facf8386b374cb74098071583d9"}
В выводе важна строка с токеном.
Токен должен быть создан в таблице sessions c UID пользователя alice
.
После создания токена можно обновить переменную session_good_token
в integration.mk.
В логе приложение должно написать
22:25:47 transport_login.go:35: ASSET: Поступил запрос на авторизацию с [::1]:36652
22:25:47 authentication.go:18: ASSET: Пользователь alice пытается авторизоваться...
22:25:47 authentication.go:24: ASSET: Пользователь alice создал сессию.
- Успешно создаём новый объект
$ make integration/create_ok
curl -v -H "Authorization: Bearer "c38fcc2c923d818b1ba765eaf5053d18"" --data body_46 "http://localhost:3000/"api/upload-asset/key_46
* processing: http://localhost:3000/api/upload-asset/key_46
* Trying [::1]:3000...
* Connected to localhost (::1) port 3000
> POST /api/upload-asset/key_46 HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.2.1
> Accept: */*
> Authorization: Bearer c38fcc2c923d818b1ba765eaf5053d18
> Content-Length: 7
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 201 Created
< Location: /api/asset/key_46
< Date: Mon, 18 Nov 2024 20:09:46 GMT
< Content-Length: 19
< Content-Type: text/plain; charset=utf-8
<
{
"status": "ok"
* Connection #0 to host localhost left intact
}
В логе приложения будет написано
22:28:42 authentication.go:34: ASSET: Сессия востановлена для пользователя: alice
22:28:42 transport_upload.go:45: ASSET: Пользователь alice восстановлен из сессии
22:28:42 transport_upload.go:53: ASSET: Пользователь alice пытается загрузить 7 байт в ключ key_42
22:28:42 assets.go:32: ASSET: Пользователь alice пытается создать данные (7 байт) по ключу key_42
22:28:42 assets.go:41: ASSET: Пользователь alice создал данные по ключу key_42 (7 байт)
22:28:42 transport_upload.go:62: ASSET: Пользователь alice успешно загрузил 7 байт в ключ key_42
В базе данных в таблице assets
будет создана запись c ключом, в данном случае key_42
.
В файле integration.mk надо обновить переменную asset_key_good
- Пробуем получить объект по ключу
$ make integration/get_ok
curl -v -H "Authorization: Bearer "c38fcc2c923d818b1ba765eaf5053d18"" "http://localhost:3000/"api/asset/"key_42"
* processing: http://localhost:3000/api/asset/key_42
* Trying [::1]:3000...
* Connected to localhost (::1) port 3000
> GET /api/asset/key_42 HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.2.1
> Accept: */*
> Authorization: Bearer c38fcc2c923d818b1ba765eaf5053d18
>
< HTTP/1.1 200 OK
< Date: Mon, 18 Nov 2024 19:32:13 GMT
< Content-Length: 7
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host localhost left intact
body_42
В лог программы будет написано:
22:32:13 authentication.go:34: ASSET: Сессия востановлена для пользователя: alice
22:32:13 transport_get.go:43: ASSET: Пользователь alice восстановлен из сессии
22:32:13 assets.go:17: ASSET: Пользователь alice пытается получить данные по ключу key_42
22:32:13 assets.go:25: ASSET: Пользователь alice получил данные по ключу key_42 (7 байт)
22:32:13 transport_get.go:70: ASSET: Пользователь alice запросил данные по ключу key_42 и получил 7 байт
Что можно улучшить в схеме БД?
- Хешировать пароль нормальным алгоритмом argon2id на стороне приложения, а не в базе данных
- Добавить constraints, чтобы не было сессий и ассетов без пользователя
Доработайте механизм авторизации таким образом, что бы в каждый момент времени у пользователя была активна только одна (последняя) сессия.
пока нет идей как это можно сделать без изменений структуры базы данных
Ограничьте максимальное время пользовательской сессии до 24-х часов.
сделал
Добавьте в БД данные об IP адресе авторизованного пользователя.
- надо менять структуру базы данных - добавлять поле типа https://www.postgresql.org/docs/current/datatype-net-types.html#DATATYPE-INET в таблицу sessions
- Если мы используем какие-либо reverse-proxy или балансировщики/CDN перед API, то реальный адрес клиента может быть или в
заголовке
X-Forwarded-For
или вCF-Connecting-IP
для Cloudflare. Также бы было неплохо указать список IP адресов reverse-proxy запросам с которых мы доверяем см. https://pkg.go.dev/github.com/gin-gonic/gin#Engine.SetTrustedProxies
Реализуйте методы API для получения списка всех закаченных файлов долго
Реализуйте методы API для удаления файлов. долго
Реализуйте работу сервера по протоколу HTTPS. См. transport.go - функция ListenHTTPS