From d2cff077807bf57d62c263a8ba96675fd5d4221c Mon Sep 17 00:00:00 2001 From: Nicolas Choquet Date: Sun, 17 Mar 2024 04:47:45 +0100 Subject: [PATCH] - Add Authentication - Use sqlite --- .gitignore | 4 +- README.md | 169 ++++++++++++++++++ actions/main.go | 17 ++ arrays/main.go | 23 +++ auth/checkToken.go | 50 ++++++ auth/generateSignatureToken.go | 69 +++++++ auth/generateToken.go | 10 ++ auth/getToken.go | 156 ++++++++++++++++ auth/main.go | 24 +++ auth/refreshToken.go | 168 +++++++++++++++++ check-validity.go => checkValidity.go | 9 +- customFs/helpers.go | 2 +- customFs/main.go | 3 + customFs/main_windows.go | 3 + customHttp/getAddr.go | 19 ++ customHttp/getUserIp.go | 37 ++++ data/main.go | 42 +++++ deleteFile.go | 32 ---- .../createDirectory.go | 20 ++- .../deleteDirectory.go | 32 ++-- directories/getDirectoryItemList.go | 48 +++++ .../renameDirectory.go | 52 ++---- file-system-service.swagger.yml | 142 ++++++++++++++- createFile.go => files/createFile.go | 22 ++- files/deleteFile.go | 44 +++++ fileFormats.go => files/formats.go | 2 +- getFileContent.go => files/getFileContent.go | 23 ++- update-file.go => files/updateFile.go | 44 +++-- flags/flags.go | 34 ++++ flags/getFlags.go | 25 +++ getFileSystem.go | 40 ----- go.mod | 19 ++ go.sum | 33 ++++ main.go | 87 ++++----- types/checkValidity.go | 5 + types/httpError.go | 6 + types/responseStatus.go | 5 + 37 files changed, 1309 insertions(+), 211 deletions(-) create mode 100644 actions/main.go create mode 100644 arrays/main.go create mode 100644 auth/checkToken.go create mode 100644 auth/generateSignatureToken.go create mode 100644 auth/generateToken.go create mode 100644 auth/getToken.go create mode 100644 auth/main.go create mode 100644 auth/refreshToken.go rename check-validity.go => checkValidity.go (66%) create mode 100644 customFs/main.go create mode 100644 customFs/main_windows.go create mode 100644 customHttp/getAddr.go create mode 100644 customHttp/getUserIp.go create mode 100644 data/main.go delete mode 100644 deleteFile.go rename createDirectory.go => directories/createDirectory.go (60%) rename deleteDirectory.go => directories/deleteDirectory.go (58%) create mode 100644 directories/getDirectoryItemList.go rename rename-directory.go => directories/renameDirectory.go (53%) rename createFile.go => files/createFile.go (72%) create mode 100644 files/deleteFile.go rename fileFormats.go => files/formats.go (93%) rename getFileContent.go => files/getFileContent.go (55%) rename update-file.go => files/updateFile.go (54%) create mode 100644 flags/flags.go create mode 100644 flags/getFlags.go delete mode 100644 getFileSystem.go create mode 100644 go.sum create mode 100644 types/checkValidity.go create mode 100644 types/httpError.go create mode 100644 types/responseStatus.go diff --git a/.gitignore b/.gitignore index 6f9ccc2..adab363 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .idea/ -.dist/ \ No newline at end of file +.dist/ + +file-system-service-oauth.sqlite \ No newline at end of file diff --git a/README.md b/README.md index 52c0106..1b5223d 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,11 @@ Click on `file-system-service.swagger.yml` ##### Get Last binary Click on `file-system-service-windows-{version}-windows-amd64.zip` +#### Generate Signature token +```shell +file-system-service --generate-signature +``` + ## API Reference #### Check Validity @@ -63,6 +68,79 @@ GET /check-validity } ``` +#### Authentification + +##### Get first token + +```http request +POST /auth/get-token +Accept: application/json +Content-Type: application/json +Signature-Token: {generated-signature} +``` +###### Response 200 + +```json +{ + "access_token": "string", + "refresh_token": "string", + "expires_in": "int", + "created_at": "int" +} +``` +###### Response 400 + +```json +{ + "code": 400, + "message": "string" +} +``` +###### Response 500 + +```json +{ + "code": 500, + "message": "string" +} +``` + +##### Refresh token + +```http request +PUT /auth/get-token +Accept: application/json +Content-Type: application/json +Signature-Token: {generated-signature} +Refresh-Token: {getted-refresh-token} +``` +###### Response 200 + +```json +{ + "access_token": "string", + "refresh_token": "string", + "expires_in": "int", + "created_at": "int" +} +``` +###### Response 400 + +```json +{ + "code": 400, + "message": "string" +} +``` +###### Response 500 + +```json +{ + "code": 500, + "message": "string" +} +``` + #### Get directory list content items ```http request @@ -109,6 +187,15 @@ GET /file-system/${path...} } ``` +##### Response 403 + +```json +{ + "code": 403, + "message": "an error message" +} +``` + #### Create a directory ```http request @@ -149,6 +236,15 @@ Content-Type: application/json } ``` +##### Response 403 + +```json +{ + "code": 403, + "message": "an error message" +} +``` + #### Rename directory ```http request @@ -195,6 +291,15 @@ Content-Type: application/json } ``` +##### Response 403 + +```json +{ + "code": 403, + "message": "an error message" +} +``` + #### Delete directory ```http request @@ -233,6 +338,15 @@ Accept: application/json } ``` +##### Response 403 + +```json +{ + "code": 403, + "message": "an error message" +} +``` + #### Create file with content ```http request @@ -282,6 +396,15 @@ Ceci est un test | :-------- | :------- | :-------------------------------- | :------------ | | `path` | `string` | **Optional**. The path of the directory you would like open | / | +##### Response 403 + +```json +{ + "code": 403, + "message": "an error message" +} +``` + #### Create file without content ```http request @@ -320,6 +443,15 @@ Content-Type: application/json | :-------- | :------- | :-------------------------------- | :------------ | | `path` | `string` | **Optional**. The path of the directory you would like open | / | +##### Response 403 + +```json +{ + "code": 403, + "message": "an error message" +} +``` + #### Get file content ```http request @@ -357,6 +489,15 @@ Accept: application/json } ``` +##### Response 403 + +```json +{ + "code": 403, + "message": "an error message" +} +``` + #### Rename selected file ```http request @@ -405,6 +546,15 @@ Content-Type: application/json } ``` +##### Response 403 + +```json +{ + "code": 403, + "message": "an error message" +} +``` + #### Update selected file content ```http request @@ -442,6 +592,15 @@ the fichier } ``` +##### Response 403 + +```json +{ + "code": 403, + "message": "an error message" +} +``` + #### Delete selected file ```http request @@ -474,3 +633,13 @@ Accept: application/json "message": "an error message" } ``` + + +##### Response 403 + +```json +{ + "code": 403, + "message": "an error message" +} +``` \ No newline at end of file diff --git a/actions/main.go b/actions/main.go new file mode 100644 index 0000000..a595bbf --- /dev/null +++ b/actions/main.go @@ -0,0 +1,17 @@ +package actions + +import ( + "filesystem_service/auth" + "filesystem_service/flags" +) + +func Exec() bool { + fl := flags.GetFlags() + + if fl.IsGenerateSignature() { + auth.GenerateSignatureTokenAction() + return true + } + + return false +} diff --git a/arrays/main.go b/arrays/main.go new file mode 100644 index 0000000..052702c --- /dev/null +++ b/arrays/main.go @@ -0,0 +1,23 @@ +package arrays + +func Map[T, U any](ts []T, f func(T) U) []U { + us := make([]U, len(ts)) + for i := range ts { + us[i] = f(ts[i]) + } + return us +} + +func Filter[T any](ts []T, f func(T) bool) []T { + us := []T{} + for i := range ts { + if pass := f(ts[i]); pass { + us = append(us, ts[i]) + } + } + return us +} + +func Generate[T any](length int) []T { + return make([]T, length) +} diff --git a/auth/checkToken.go b/auth/checkToken.go new file mode 100644 index 0000000..0a14ab0 --- /dev/null +++ b/auth/checkToken.go @@ -0,0 +1,50 @@ +package auth + +import ( + "filesystem_service/customHttp" + "filesystem_service/data" + "fmt" + "net/http" + "strings" +) + +func CheckToken(request *http.Request) (bool, error) { + signature := request.Header.Get("Signature-Token") + token := strings.Replace(request.Header.Get("Authorization"), "Bearer ", "", 1) + + if token == "" || signature == "" { + return false, fmt.Errorf("you are not authorized") + } + + ip, err := customHttp.GetUserIp(request) + if err != nil { + return false, err + } + + db, err := data.InitDatabase() + defer db.Close() + if err != nil { + return false, err + } + + results, err := db.Query(fmt.Sprintf( + "SELECT * FROM tokens WHERE IP=\"%v\" AND active=TRUE AND type=\"classic\" AND token=\"%s\" AND signature=\"%s\"", + ip, token, signature, + )) + if err != nil { + return false, err + } + + tokens, err := data.ReadRows[Token](results, func(t *Token) error { + return results.Scan(&t.Id, &t.Ip, &t.Token, &t.Signature, &t.Type, &t.Active, &t.CreatedAt) + }) + if err != nil { + return false, err + } + + if len(tokens) == 0 { + return false, fmt.Errorf("invalid access token") + } + + return true, nil +} diff --git a/auth/generateSignatureToken.go b/auth/generateSignatureToken.go new file mode 100644 index 0000000..344c838 --- /dev/null +++ b/auth/generateSignatureToken.go @@ -0,0 +1,69 @@ +package auth + +import ( + "filesystem_service/arrays" + "filesystem_service/data" + "fmt" + "math/rand" + "strings" + + "github.com/atotto/clipboard" +) + +func randInt(min, max int) int { + return min + rand.Intn(max-min) +} + +var availableCharacters = "aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ0123456789+-*/=!&@#'()|\\{}[]_~" + +func getNewToken(availableCharacters string, charNumber int) string { + return strings.Join( + arrays.Map( + arrays.Generate[string](charNumber), + func(t string) string { + return strings.Split(availableCharacters, "")[randInt(0, len(availableCharacters)-1)] + }, + ), + "", + ) +} + +func generateSignatureToken() string { + return getNewToken(availableCharacters, 60) +} + +func GenerateSignatureTokenAction() { + fmt.Printf("Generation du token de signature ...\n") + token := generateSignatureToken() + err := clipboard.WriteAll(token) + db, err := data.InitDatabase() + defer db.Close() + if err != nil { + fmt.Printf("Une erreur est survenue lors de l'initialisation de la base de donnée.\n") + fmt.Printf(err.Error() + "\n") + return + } + + _, err = db.Exec(`UPDATE signatures SET active=FALSE where active=TRUE`, token) + if err != nil { + fmt.Printf("Une erreur est survenue lors de la création du token de signature.\n") + fmt.Printf(err.Error() + "\n") + return + } + + _, err = db.Exec(`INSERT INTO signatures (signature) VALUES (?)`, token) + if err != nil { + fmt.Printf("Une erreur est survenue lors de la création du token de signature.\n") + fmt.Printf(err.Error() + "\n") + return + } + + if err != nil { + fmt.Printf("Vous devrez le saisir dans le système d'exploitation web.\n") + fmt.Printf("Nous n'avons pas pu le mettre dans votre press-papier mais le voici ci-dessous:\n") + } else { + fmt.Printf("Nous l'avons mis dans votre presse-papier.\n") + fmt.Printf("Vous devrez le saisir dans le système d'exploitation web.\n") + } + fmt.Printf("\n=> %v\n", token) +} diff --git a/auth/generateToken.go b/auth/generateToken.go new file mode 100644 index 0000000..8ac8391 --- /dev/null +++ b/auth/generateToken.go @@ -0,0 +1,10 @@ +package auth + +import "fmt" + +func GenerateToken(signature string) (string, error) { + if signature == "" { + return "", fmt.Errorf("signature is empty") + } + return getNewToken(signature, 60), nil +} diff --git a/auth/getToken.go b/auth/getToken.go new file mode 100644 index 0000000..a0c245c --- /dev/null +++ b/auth/getToken.go @@ -0,0 +1,156 @@ +package auth + +import ( + "encoding/json" + "filesystem_service/customHttp" + "filesystem_service/data" + "filesystem_service/types" + "net/http" + "time" +) + +func GetToken(writer http.ResponseWriter, request *http.Request) { + writer.Header().Set("Access-Control-Allow-Origin", "*") + writer.Header().Add("Content-Type", "application/json") + db, err := data.InitDatabase() + defer db.Close() + if err != nil { + writer.WriteHeader(500) + response, _ := json.Marshal(types.HttpError{ + Code: 500, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + signatureToken := request.Header.Get("Signature-Token") + + if signatureToken == "" { + writer.WriteHeader(400) + response, _ := json.Marshal(types.HttpError{ + Code: 400, + Message: "You must input your signature token", + }) + _, _ = writer.Write(response) + return + } + + foundSignatures, err := db.Query( + `SELECT * FROM signatures WHERE active=TRUE AND signature=?`, + signatureToken, + ) + if err != nil { + writer.WriteHeader(500) + response, _ := json.Marshal(types.HttpError{ + Code: 500, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + + signatures, err := data.ReadRows[Signature](foundSignatures, func(t *Signature) error { + return foundSignatures.Scan(&t.Id, &t.Signature, &t.Active) + }) + if err != nil { + writer.WriteHeader(500) + response, _ := json.Marshal(types.HttpError{ + Code: 500, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + + if len(signatures) == 0 { + writer.WriteHeader(403) + response, _ := json.Marshal(types.HttpError{ + Code: 403, + Message: "You are not identified", + }) + _, _ = writer.Write(response) + return + } + + signature := signatures[0].Signature + token, err := GenerateToken(signature) + if err != nil { + writer.WriteHeader(500) + response, _ := json.Marshal(types.HttpError{ + Code: 500, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + refreshToken, err := GenerateToken(token) + if err != nil { + writer.WriteHeader(500) + response, _ := json.Marshal(types.HttpError{ + Code: 500, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + + ip, err := customHttp.GetUserIp(request) + if err != nil { + writer.WriteHeader(500) + response, _ := json.Marshal(types.HttpError{ + Code: 500, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + + _, err = db.Exec(`UPDATE tokens SET active=FALSE WHERE IP=? AND active=TRUE`, ip) + if err != nil { + writer.WriteHeader(500) + response, _ := json.Marshal(types.HttpError{ + Code: 500, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + + _, err = db.Exec( + `INSERT INTO tokens (IP, token, signature, type, created_at) VALUES (?, ?, ?, ?, ?)`, + ip, token, signature, "classic", time.Now().Unix(), + ) + if err != nil { + writer.WriteHeader(500) + response, _ := json.Marshal(types.HttpError{ + Code: 500, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + _, err = db.Exec( + `INSERT INTO tokens (IP, token, signature, type, created_at) VALUES (?, ?, ?, ?, ?)`, + ip, refreshToken, signature, "refresh", time.Now().Unix(), + ) + if err != nil { + writer.WriteHeader(500) + response, _ := json.Marshal(types.HttpError{ + Code: 500, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + + tokenResponse := &GetTokenResponse{ + AccessToken: token, + RefreshToken: refreshToken, + CreatedAt: time.Now().Unix(), + // 1h (en ms) + ExpiresIn: 3600000, + } + + response, _ := json.Marshal(tokenResponse) + _, _ = writer.Write(response) +} diff --git a/auth/main.go b/auth/main.go new file mode 100644 index 0000000..4bcbbcc --- /dev/null +++ b/auth/main.go @@ -0,0 +1,24 @@ +package auth + +type GetTokenResponse struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + CreatedAt int64 `json:"created_at"` + ExpiresIn int `json:"expires_in"` +} + +type Signature struct { + Id int `json:"id"` + Signature string `json:"signature"` + Active bool `json:"active"` +} + +type Token struct { + Id int `json:"id"` + Ip string + Token string `json:"token"` + Signature string `json:"signature"` + Type string `json:"type"` + Active bool `json:"active"` + CreatedAt int `json:"created_at"` +} diff --git a/auth/refreshToken.go b/auth/refreshToken.go new file mode 100644 index 0000000..726392c --- /dev/null +++ b/auth/refreshToken.go @@ -0,0 +1,168 @@ +package auth + +import ( + "encoding/json" + "filesystem_service/arrays" + "filesystem_service/customHttp" + "filesystem_service/data" + "filesystem_service/types" + "fmt" + "net/http" + "time" +) + +func RefreshToken(writer http.ResponseWriter, request *http.Request) { + writer.Header().Set("Access-Control-Allow-Origin", "*") + writer.Header().Add("Content-Type", "application/json") + db, err := data.InitDatabase() + defer db.Close() + if err != nil { + writer.WriteHeader(500) + response, _ := json.Marshal(types.HttpError{ + Code: 500, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + refreshTokenHeader := request.Header.Get("Refresh-Token") + signatureTokenHeader := request.Header.Get("Signature-Token") + + if refreshTokenHeader == "" { + writer.WriteHeader(400) + response, _ := json.Marshal(types.HttpError{ + Code: 400, + Message: "Invalid refresh token", + }) + _, _ = writer.Write(response) + return + } + + ip, err := customHttp.GetUserIp(request) + if err != nil { + writer.WriteHeader(500) + response, _ := json.Marshal(types.HttpError{ + Code: 500, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + + tokens, err := db.Query(fmt.Sprintf( + "SELECT * FROM tokens WHERE IP=\"%v\" AND active=TRUE AND signature=\"%v\"", + ip, signatureTokenHeader, + )) + if err != nil { + writer.WriteHeader(500) + response, _ := json.Marshal(types.HttpError{ + Code: 500, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + + formattedTokens, err := data.ReadRows[Token](tokens, func(t *Token) error { + return tokens.Scan(&t.Id, &t.Ip, &t.Token, &t.Signature, &t.Type, &t.Active, &t.CreatedAt) + }) + + _, err = json.Marshal(&formattedTokens) + + if err != nil { + println(err.Error()) + } + + if len(formattedTokens) == 0 { + writer.WriteHeader(400) + response, _ := json.Marshal(types.HttpError{ + Code: 400, + Message: "Invalid couple signature and refresh token", + }) + _, _ = writer.Write(response) + return + } + + refreshTokenObj := arrays.Filter[Token](formattedTokens, func(token Token) bool { + return token.Type == "refresh" + })[0] + + if refreshTokenHeader != refreshTokenObj.Token { + writer.WriteHeader(400) + response, _ := json.Marshal(types.HttpError{ + Code: 400, + Message: "Invalid refresh token", + }) + _, _ = writer.Write(response) + return + } + + token, err := GenerateToken(signatureTokenHeader + "." + refreshTokenHeader) + if err != nil { + writer.WriteHeader(500) + response, _ := json.Marshal(types.HttpError{ + Code: 500, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + refreshToken, err := GenerateToken(token) + if err != nil { + writer.WriteHeader(500) + response, _ := json.Marshal(types.HttpError{ + Code: 500, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + + _, err = db.Exec(`UPDATE tokens SET active=FALSE WHERE IP=? AND active=TRUE`, ip) + if err != nil { + writer.WriteHeader(500) + response, _ := json.Marshal(types.HttpError{ + Code: 500, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + + _, err = db.Exec( + `INSERT INTO tokens (IP, token, signature, type, created_at) VALUES (?, ?, ?, ?, ?)`, + ip, token, signatureTokenHeader, "classic", time.Now().Unix(), + ) + if err != nil { + writer.WriteHeader(500) + response, _ := json.Marshal(types.HttpError{ + Code: 500, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + + _, err = db.Exec( + `INSERT INTO tokens (IP, token, signature, type, created_at) VALUES (?, ?, ?, ?, ?)`, + ip, refreshToken, signatureTokenHeader, "refresh", time.Now().Unix(), + ) + if err != nil { + writer.WriteHeader(500) + response, _ := json.Marshal(types.HttpError{ + Code: 500, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + + response, _ := json.Marshal(&GetTokenResponse{ + AccessToken: token, + RefreshToken: refreshToken, + CreatedAt: time.Now().Unix(), + // 1h (en ms) + ExpiresIn: 3600000, + }) + _, _ = writer.Write(response) +} diff --git a/check-validity.go b/checkValidity.go similarity index 66% rename from check-validity.go rename to checkValidity.go index d091f53..5c38b0a 100644 --- a/check-validity.go +++ b/checkValidity.go @@ -2,18 +2,15 @@ package main import ( "encoding/json" + "filesystem_service/types" "net/http" ) -type CheckValidity struct { - IsValid bool `json:"isValid"` -} - -func checkValidity(writer http.ResponseWriter, request *http.Request) { +func CheckValidity(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Access-Control-Allow-Origin", "*") writer.Header().Add("Content-Type", "application/json") - response := &CheckValidity{true} + response := &types.CheckValidity{true} _json, err := json.Marshal(response) if err != nil { diff --git a/customFs/helpers.go b/customFs/helpers.go index 45f38c7..e293ffd 100644 --- a/customFs/helpers.go +++ b/customFs/helpers.go @@ -27,7 +27,7 @@ func getParentPath(path string) string { finalPath := strings.Join(parts[0:len(parts)-1], "/") if finalPath == "" { - finalPath = "/" + finalPath + finalPath = ROOT + finalPath } return finalPath } diff --git a/customFs/main.go b/customFs/main.go new file mode 100644 index 0000000..f27c468 --- /dev/null +++ b/customFs/main.go @@ -0,0 +1,3 @@ +package customFs + +const ROOT = "/" diff --git a/customFs/main_windows.go b/customFs/main_windows.go new file mode 100644 index 0000000..f5464d7 --- /dev/null +++ b/customFs/main_windows.go @@ -0,0 +1,3 @@ +package customFs + +const ROOT = "C://" diff --git a/customHttp/getAddr.go b/customHttp/getAddr.go new file mode 100644 index 0000000..c924a3d --- /dev/null +++ b/customHttp/getAddr.go @@ -0,0 +1,19 @@ +package customHttp + +import ( + "filesystem_service/flags" + "fmt" + "strconv" + "strings" +) + +func GetAddr() string { + fl := flags.GetFlags() + + host := fl.GetHost() + if host != "" && strings.Contains(host, ":") { + host = fmt.Sprintf("[%s]", host) + } + + return fmt.Sprintf("%v:%v", host, strconv.Itoa(fl.GetPort())) +} diff --git a/customHttp/getUserIp.go b/customHttp/getUserIp.go new file mode 100644 index 0000000..5abd14c --- /dev/null +++ b/customHttp/getUserIp.go @@ -0,0 +1,37 @@ +package customHttp + +import ( + "errors" + "net" + "net/http" + "strings" +) + +func GetUserIp(r *http.Request) (string, error) { + ips := r.Header.Get("X-Forwarded-For") + splitIps := strings.Split(ips, ",") + + if len(splitIps) > 0 { + // get last IP in list since ELB prepends other user defined IPs, meaning the last one is the actual client IP. + netIP := net.ParseIP(splitIps[len(splitIps)-1]) + if netIP != nil { + return netIP.String(), nil + } + } + + ip, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + return "", err + } + + netIP := net.ParseIP(ip) + if netIP != nil { + ip := netIP.String() + if ip == "::1" { + return "127.0.0.1", nil + } + return ip, nil + } + + return "", errors.New("IP not found") +} diff --git a/data/main.go b/data/main.go new file mode 100644 index 0000000..32ef11f --- /dev/null +++ b/data/main.go @@ -0,0 +1,42 @@ +package data + +import ( + "database/sql" +) + +func ConnectDatabase() (*sql.DB, error) { + return sql.Open("sqlite", "./file-system-service-oauth.sqlite") +} + +func InitDatabase() (*sql.DB, error) { + db, err := ConnectDatabase() + if err != nil { + return nil, err + } + + //ENUM('classic', 'refresh') + _, err = db.Exec("CREATE TABLE IF NOT EXISTS tokens (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, IP TEXT NOT NULL, token TEXT, signature TEXT, type TEXT NOT NULL DEFAULT 'classic', active BOOLEAN NOT NULL DEFAULT true, created_at DATETIME NOT NULL)") + if err != nil { + return nil, err + } + + _, err = db.Exec("CREATE TABLE IF NOT EXISTS signatures (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, signature TEXT, active BOOLEAN DEFAULT true)") + if err != nil { + return nil, err + } + + return db, nil +} + +func ReadRows[T any](rows *sql.Rows, scan func(t *T) error) (results []T, _err error) { + _err = nil + for rows.Next() { + var line T + if err := scan(&line); err != nil { + _err = err + break + } + results = append(results, line) + } + return results, _err +} diff --git a/deleteFile.go b/deleteFile.go deleted file mode 100644 index 698ef4e..0000000 --- a/deleteFile.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "encoding/json" - fs "filesystem_service/customFs" - "net/http" - "strings" -) - -func deleteFile(writer http.ResponseWriter, request *http.Request) { - writer.Header().Set("Access-Control-Allow-Origin", "*") - writer.Header().Add("Content-Type", "application/json") - - path := "/" + strings.Replace(request.PathValue("path"), "%2F", "/", -1) - - f := fs.NewFile(path) - - if _, err := f.Delete(); err != nil { - writer.WriteHeader(400) - response, _ := json.Marshal(&HttpError{ - Code: 400, - Message: err.Error(), - }) - _, _ = writer.Write(response) - return - } - - response, _ := json.Marshal(&ResponseStatus{ - Status: "success", - }) - _, _ = writer.Write(response) -} diff --git a/createDirectory.go b/directories/createDirectory.go similarity index 60% rename from createDirectory.go rename to directories/createDirectory.go index 2326cd6..da11780 100644 --- a/createDirectory.go +++ b/directories/createDirectory.go @@ -1,20 +1,32 @@ -package main +package directories import ( "encoding/json" + "filesystem_service/auth" fs "filesystem_service/customFs" + "filesystem_service/types" "net/http" ) -func createDirectory(writer http.ResponseWriter, request *http.Request) { +func CreateDirectory(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Access-Control-Allow-Origin", "*") writer.Header().Add("Content-Type", "application/json") + if _, err := auth.CheckToken(request); err != nil { + writer.WriteHeader(403) + response, _ := json.Marshal(&types.HttpError{ + Code: 403, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + var body fs.Directory if err := json.NewDecoder(request.Body).Decode(&body); err != nil { writer.WriteHeader(400) - response, _ := json.Marshal(&HttpError{ + response, _ := json.Marshal(&types.HttpError{ Code: 400, Message: err.Error(), }) @@ -26,7 +38,7 @@ func createDirectory(writer http.ResponseWriter, request *http.Request) { if _, err := d.Create(); err != nil { writer.WriteHeader(400) - response, _ := json.Marshal(&HttpError{ + response, _ := json.Marshal(&types.HttpError{ Code: 400, Message: err.Error(), }) diff --git a/deleteDirectory.go b/directories/deleteDirectory.go similarity index 58% rename from deleteDirectory.go rename to directories/deleteDirectory.go index 95f70d7..7f365cd 100644 --- a/deleteDirectory.go +++ b/directories/deleteDirectory.go @@ -1,33 +1,34 @@ -package main +package directories import ( "encoding/json" + "filesystem_service/auth" fs "filesystem_service/customFs" + "filesystem_service/types" "net/http" "strings" ) -func deleteDirectory(writer http.ResponseWriter, request *http.Request) { +func DeleteDirectory(writer http.ResponseWriter, request *http.Request) { writer.Header().Add("Content-Type", "application/json") writer.Header().Set("Access-Control-Allow-Origin", "*") - isForce := request.URL.Query().Has("force") - - path := "/" + strings.Replace(request.PathValue("path"), "%2F", "/", -1) - - d := fs.NewDirectory(path) - /*fi, err := os.Stat(path) - if err != nil { - writer.WriteHeader(400) - response, _ := json.Marshal(&HttpError{ - Code: 400, + if _, err := auth.CheckToken(request); err != nil { + writer.WriteHeader(403) + response, _ := json.Marshal(&types.HttpError{ + Code: 403, Message: err.Error(), }) _, _ = writer.Write(response) return } - if fi.Mode().IsDir() {*/ + isForce := request.URL.Query().Has("force") + + path := fs.ROOT + strings.Replace(request.PathValue("path"), "%2F", "/", -1) + + d := fs.NewDirectory(path) + var err error if isForce { _, err = d.DeepDelete() @@ -37,16 +38,15 @@ func deleteDirectory(writer http.ResponseWriter, request *http.Request) { if err != nil { writer.WriteHeader(400) - response, _ := json.Marshal(&HttpError{ + response, _ := json.Marshal(&types.HttpError{ Code: 400, Message: err.Error(), }) _, _ = writer.Write(response) return } - //} - response, _ := json.Marshal(&ResponseStatus{ + response, _ := json.Marshal(&types.ResponseStatus{ Status: "success", }) diff --git a/directories/getDirectoryItemList.go b/directories/getDirectoryItemList.go new file mode 100644 index 0000000..bbb3b84 --- /dev/null +++ b/directories/getDirectoryItemList.go @@ -0,0 +1,48 @@ +package directories + +import ( + "encoding/json" + "filesystem_service/auth" + fs "filesystem_service/customFs" + "filesystem_service/types" + "net/http" + "strings" +) + +func GetFileSystem(writer http.ResponseWriter, request *http.Request) { + writer.Header().Set("Access-Control-Allow-Origin", "*") + writer.Header().Add("Content-Type", "application/json") + + if _, err := auth.CheckToken(request); err != nil { + writer.WriteHeader(403) + response, _ := json.Marshal(&types.HttpError{ + Code: 403, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + + path := fs.ROOT + strings.Replace(request.PathValue("path"), "%2F", "/", -1) + + d := fs.NewDirectory(path) + list, err := d.GetFlatContent() + + if err != nil { + code := 400 + if strings.Contains(err.Error(), "no such file or directory") { + code = 404 + } + + writer.WriteHeader(code) + response, _ := json.Marshal(&types.HttpError{ + Code: code, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + + response, _ := json.Marshal(list) + _, _ = writer.Write(response) +} diff --git a/rename-directory.go b/directories/renameDirectory.go similarity index 53% rename from rename-directory.go rename to directories/renameDirectory.go index 92dd2b1..eeea670 100644 --- a/rename-directory.go +++ b/directories/renameDirectory.go @@ -1,21 +1,33 @@ -package main +package directories import ( "encoding/json" + "filesystem_service/auth" fs "filesystem_service/customFs" + "filesystem_service/types" "net/http" "strings" ) -func renameDirectory(writer http.ResponseWriter, request *http.Request) { +func RenameDirectory(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Access-Control-Allow-Origin", "*") writer.Header().Add("Content-Type", "application/json") + if _, err := auth.CheckToken(request); err != nil { + writer.WriteHeader(403) + response, _ := json.Marshal(&types.HttpError{ + Code: 403, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + var body fs.Directory if err := json.NewDecoder(request.Body).Decode(&body); err != nil { writer.WriteHeader(400) - response, _ := json.Marshal(&HttpError{ + response, _ := json.Marshal(&types.HttpError{ Code: 400, Message: err.Error(), }) @@ -23,25 +35,14 @@ func renameDirectory(writer http.ResponseWriter, request *http.Request) { return } - path := "/" + strings.Replace(request.PathValue("path"), "%2F", "/", -1) + path := fs.ROOT + strings.Replace(request.PathValue("path"), "%2F", "/", -1) d := fs.NewDirectory(path) nd := fs.NewDirectory(fs.BuildDirectoryCompletePath(body)) if _, err := d.Rename(nd); err != nil { writer.WriteHeader(400) - response, _ := json.Marshal(&HttpError{ - Code: 400, - Message: err.Error(), - }) - _, _ = writer.Write(response) - return - } - - /*fi, err := os.Stat(path) - if err != nil { - writer.WriteHeader(400) - response, _ := json.Marshal(&HttpError{ + response, _ := json.Marshal(&types.HttpError{ Code: 400, Message: err.Error(), }) @@ -49,25 +50,6 @@ func renameDirectory(writer http.ResponseWriter, request *http.Request) { return } - if fi.Mode().IsDir() { - newPath := body.Path - if newPath != "/" { - newPath += "/" - } - newPath += body.Name - - err = os.Rename(path, newPath) - if err != nil { - writer.WriteHeader(400) - response, _ := json.Marshal(&HttpError{ - Code: 400, - Message: err.Error(), - }) - _, _ = writer.Write(response) - return - } - }*/ - response, _ := json.Marshal(&body) _, _ = writer.Write(response) diff --git a/file-system-service.swagger.yml b/file-system-service.swagger.yml index 88e17c8..1d45a89 100644 --- a/file-system-service.swagger.yml +++ b/file-system-service.swagger.yml @@ -1,8 +1,8 @@ -openapi: '3.0.2' +openapi: '3.0.3' info: title: File system Service - version: 0.4.0 + version: 0.9.0 servers: - url: http://{host}:{port} @@ -77,7 +77,21 @@ components: - "success" - "error" + AccessToken: + type: object + properties: + access_token: + type: string + refresh_token: + type: string + created_at: + type: integer + expires_in: + type: integer + tags: + - name: Auth + description: API Authentication - name: File System description: All concerns file system. - name: Files @@ -86,6 +100,72 @@ tags: description: All concerns directories. paths: + /auth/get-token: + post: + tags: + - Auth + description: Create first token + parameters: + - name: Signature-Token + in: header + schema: + type: string + required: true + responses: + '200': + description: Get token + content: + application/json: + schema: + $ref: '#/components/schemas/AccessToken' + '400': + description: Get token + content: + application/json: + schema: + $ref: '#/components/schemas/HttpError' + '500': + description: Get token + content: + application/json: + schema: + $ref: '#/components/schemas/HttpError' + + put: + tags: + - Auth + description: Create new token from refresh token + parameters: + - name: Signature-Token + in: header + schema: + type: string + required: true + - name: Refresh-Token + in: header + schema: + type: string + required: true + responses: + '200': + description: Get token + content: + application/json: + schema: + $ref: '#/components/schemas/AccessToken' + '400': + description: Get token + content: + application/json: + schema: + $ref: '#/components/schemas/HttpError' + '500': + description: Get token + content: + application/json: + schema: + $ref: '#/components/schemas/HttpError' + /check-validity: get: description: New endpoint @@ -135,6 +215,14 @@ paths: application/json: schema: $ref: '#/components/schemas/HttpError' + + '403': + description: Unauthorized + + content: + application/json: + schema: + $ref: '#/components/schemas/HttpError' /file-system: summary: Get all content of root directory (flat) @@ -161,6 +249,14 @@ paths: schema: $ref: '#/components/schemas/HttpError' + '403': + description: Unauthorized + + content: + application/json: + schema: + $ref: '#/components/schemas/HttpError' + /directory: summary: Create new directory description: Create new directory @@ -197,6 +293,14 @@ paths: schema: $ref: '#/components/schemas/HttpError' + '403': + description: Unauthorized + + content: + application/json: + schema: + $ref: '#/components/schemas/HttpError' + /directory/{path}: parameters: - name: path @@ -245,6 +349,14 @@ paths: schema: $ref: '#/components/schemas/HttpError' + '403': + description: Unauthorized + + content: + application/json: + schema: + $ref: '#/components/schemas/HttpError' + delete: summary: Delete a directory description: Delete a directory @@ -277,6 +389,14 @@ paths: schema: $ref: '#/components/schemas/ResponseStatus' + '403': + description: Unauthorized + + content: + application/json: + schema: + $ref: '#/components/schemas/HttpError' + /file: summary: Create new file description: Create new file with or without content @@ -323,6 +443,14 @@ paths: schema: $ref: '#/components/schemas/HttpError' + '403': + description: Unauthorized + + content: + application/json: + schema: + $ref: '#/components/schemas/HttpError' + /file/{path}: parameters: - name: path @@ -381,6 +509,14 @@ paths: schema: $ref: '#/components/schemas/HttpError' + '403': + description: Unauthorized + + content: + application/json: + schema: + $ref: '#/components/schemas/HttpError' + patch: summary: Rename selected file description: Rename selected file @@ -499,4 +635,4 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/HttpError' \ No newline at end of file + $ref: '#/components/schemas/HttpError' diff --git a/createFile.go b/files/createFile.go similarity index 72% rename from createFile.go rename to files/createFile.go index ac10efe..63c203c 100644 --- a/createFile.go +++ b/files/createFile.go @@ -1,8 +1,10 @@ -package main +package files import ( "encoding/json" + "filesystem_service/auth" fs "filesystem_service/customFs" + "filesystem_service/types" "net/http" ) @@ -25,15 +27,25 @@ func getMultipartKeys(request *http.Request) (file fs.File, content string, err return } -func createFile(writer http.ResponseWriter, request *http.Request) { +func CreateFile(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Access-Control-Allow-Origin", "*") writer.Header().Add("Content-Type", "application/json") + if _, err := auth.CheckToken(request); err != nil { + writer.WriteHeader(403) + response, _ := json.Marshal(&types.HttpError{ + Code: 403, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + file, content, err := getMultipartKeys(request) if err != nil { writer.Header().Add("Content-Type", "application/json") writer.WriteHeader(400) - response, _ := json.Marshal(&HttpError{ + response, _ := json.Marshal(&types.HttpError{ Code: 400, Message: err.Error(), }) @@ -45,7 +57,7 @@ func createFile(writer http.ResponseWriter, request *http.Request) { if _, err = f.Create(); err != nil { writer.WriteHeader(400) - response, _ := json.Marshal(&HttpError{ + response, _ := json.Marshal(&types.HttpError{ Code: 400, Message: err.Error(), }) @@ -55,7 +67,7 @@ func createFile(writer http.ResponseWriter, request *http.Request) { if _, err = f.SetContent([]byte(content)); err != nil { writer.WriteHeader(400) - response, _ := json.Marshal(&HttpError{ + response, _ := json.Marshal(&types.HttpError{ Code: 400, Message: err.Error(), }) diff --git a/files/deleteFile.go b/files/deleteFile.go new file mode 100644 index 0000000..ed8b046 --- /dev/null +++ b/files/deleteFile.go @@ -0,0 +1,44 @@ +package files + +import ( + "encoding/json" + "filesystem_service/auth" + fs "filesystem_service/customFs" + "filesystem_service/types" + "net/http" + "strings" +) + +func DeleteFile(writer http.ResponseWriter, request *http.Request) { + writer.Header().Set("Access-Control-Allow-Origin", "*") + writer.Header().Add("Content-Type", "application/json") + + if _, err := auth.CheckToken(request); err != nil { + writer.WriteHeader(403) + response, _ := json.Marshal(&types.HttpError{ + Code: 403, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + + path := fs.ROOT + strings.Replace(request.PathValue("path"), "%2F", "/", -1) + + f := fs.NewFile(path) + + if _, err := f.Delete(); err != nil { + writer.WriteHeader(400) + response, _ := json.Marshal(&types.HttpError{ + Code: 400, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + + response, _ := json.Marshal(&types.ResponseStatus{ + Status: "success", + }) + _, _ = writer.Write(response) +} diff --git a/fileFormats.go b/files/formats.go similarity index 93% rename from fileFormats.go rename to files/formats.go index 4170300..0c0c972 100644 --- a/fileFormats.go +++ b/files/formats.go @@ -1,4 +1,4 @@ -package main +package files var fileFormats = map[string]string{ ".json": "application/json", diff --git a/getFileContent.go b/files/getFileContent.go similarity index 55% rename from getFileContent.go rename to files/getFileContent.go index 25a6be5..da10357 100644 --- a/getFileContent.go +++ b/files/getFileContent.go @@ -1,17 +1,30 @@ -package main +package files import ( "encoding/json" + "filesystem_service/auth" fs "filesystem_service/customFs" + "filesystem_service/types" "fmt" "net/http" "strings" ) -func getFileContent(writer http.ResponseWriter, request *http.Request) { +func GetFileContent(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Access-Control-Allow-Origin", "*") - path := "/" + strings.Replace(request.PathValue("path"), "%2F", "/", -1) + if _, err := auth.CheckToken(request); err != nil { + writer.Header().Add("Content-Type", "application/json") + writer.WriteHeader(403) + response, _ := json.Marshal(&types.HttpError{ + Code: 403, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + + path := fs.ROOT + strings.Replace(request.PathValue("path"), "%2F", "/", -1) file := fs.NewFile(path) _, err := file.IsFile() @@ -19,7 +32,7 @@ func getFileContent(writer http.ResponseWriter, request *http.Request) { if err != nil { writer.Header().Add("Content-Type", "application/json") writer.WriteHeader(400) - response, _ := json.Marshal(&HttpError{ + response, _ := json.Marshal(&types.HttpError{ Code: 400, Message: err.Error(), }) @@ -32,7 +45,7 @@ func getFileContent(writer http.ResponseWriter, request *http.Request) { writer.Header().Add("Content-Type", fileFormats[extension]) fileContent, err := file.GetContent() if err != nil { - response, _ := json.Marshal(&HttpError{ + response, _ := json.Marshal(&types.HttpError{ Code: 404, Message: fmt.Sprintf("open %s not found", path), }) diff --git a/update-file.go b/files/updateFile.go similarity index 54% rename from update-file.go rename to files/updateFile.go index 93427c9..9591046 100644 --- a/update-file.go +++ b/files/updateFile.go @@ -1,24 +1,36 @@ -package main +package files import ( "encoding/json" + "filesystem_service/auth" fs "filesystem_service/customFs" + "filesystem_service/types" "io" "net/http" "strings" ) -func renameFile(writer http.ResponseWriter, request *http.Request) { +func RenameFile(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Access-Control-Allow-Origin", "*") writer.Header().Add("Content-Type", "application/json") + if _, err := auth.CheckToken(request); err != nil { + writer.WriteHeader(403) + response, _ := json.Marshal(&types.HttpError{ + Code: 403, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + var renamedFile fs.File - path := "/" + strings.Replace(request.PathValue("path"), "%2F", "/", -1) + path := fs.ROOT + strings.Replace(request.PathValue("path"), "%2F", "/", -1) if err := json.NewDecoder(request.Body).Decode(&renamedFile); err != nil { writer.WriteHeader(400) - response, _ := json.Marshal(&HttpError{ + response, _ := json.Marshal(&types.HttpError{ Code: 400, Message: err.Error(), }) @@ -30,7 +42,7 @@ func renameFile(writer http.ResponseWriter, request *http.Request) { if _, err := f.Rename(fs.NewFile(fs.BuildFileCompletePath(renamedFile))); err != nil { writer.WriteHeader(400) - response, _ := json.Marshal(&HttpError{ + response, _ := json.Marshal(&types.HttpError{ Code: 400, Message: err.Error(), }) @@ -38,17 +50,27 @@ func renameFile(writer http.ResponseWriter, request *http.Request) { return } - response, _ := json.Marshal(&ResponseStatus{ + response, _ := json.Marshal(&types.ResponseStatus{ Status: "success", }) _, _ = writer.Write(response) } -func updateFileContent(writer http.ResponseWriter, request *http.Request) { +func UpdateFileContent(writer http.ResponseWriter, request *http.Request) { writer.Header().Set("Access-Control-Allow-Origin", "*") writer.Header().Add("Content-Type", "application/json") - path := "/" + strings.Replace(request.PathValue("path"), "%2F", "/", -1) + if _, err := auth.CheckToken(request); err != nil { + writer.WriteHeader(403) + response, _ := json.Marshal(&types.HttpError{ + Code: 403, + Message: err.Error(), + }) + _, _ = writer.Write(response) + return + } + + path := fs.ROOT + strings.Replace(request.PathValue("path"), "%2F", "/", -1) var body []byte @@ -57,7 +79,7 @@ func updateFileContent(writer http.ResponseWriter, request *http.Request) { text, err := io.ReadAll(request.Body) if err != nil { writer.WriteHeader(400) - response, _ := json.Marshal(&HttpError{ + response, _ := json.Marshal(&types.HttpError{ Code: 400, Message: err.Error(), }) @@ -68,7 +90,7 @@ func updateFileContent(writer http.ResponseWriter, request *http.Request) { if _, err = f.SetContent(body); err != nil { writer.WriteHeader(400) - response, _ := json.Marshal(&HttpError{ + response, _ := json.Marshal(&types.HttpError{ Code: 400, Message: err.Error(), }) @@ -76,7 +98,7 @@ func updateFileContent(writer http.ResponseWriter, request *http.Request) { return } - response, _ := json.Marshal(&ResponseStatus{ + response, _ := json.Marshal(&types.ResponseStatus{ Status: "success", }) _, _ = writer.Write(response) diff --git a/flags/flags.go b/flags/flags.go new file mode 100644 index 0000000..4eed4ab --- /dev/null +++ b/flags/flags.go @@ -0,0 +1,34 @@ +package flags + +import ( + "os" + "strconv" +) + +type Flags struct { + port *int + host *string + portEnv *string + hostEnv *string + + generateSignature *bool +} + +func (f *Flags) GetPort() int { + if envPort := os.Getenv(*f.portEnv); f.portEnv != nil && envPort != "" { + port, _ := strconv.Atoi(envPort) + return port + } + return *f.port +} + +func (f *Flags) GetHost() string { + if envHost := os.Getenv(*f.hostEnv); f.hostEnv != nil && envHost != "" { + return envHost + } + return *f.host +} + +func (f *Flags) IsGenerateSignature() bool { + return !(f.generateSignature == nil || *f.generateSignature == false) +} diff --git a/flags/getFlags.go b/flags/getFlags.go new file mode 100644 index 0000000..4aa2395 --- /dev/null +++ b/flags/getFlags.go @@ -0,0 +1,25 @@ +package flags + +import "flag" + +var port = flag.Int("port", 3000, "Port d'exposition de l'application.") +var host = flag.String("host", "127.0.0.1", "Domaine ou IP de la machine qui expose le sercice.") + +var portEnv = flag.String("port-env", "", "Variable d'environement où trouver le port d'exposition de l'application.") +var hostEnv = flag.String("host-env", "", "Variable d'environement où trouver le domaine ou l'IP de la machine qui expose le sercice.") + +var generateSignature = flag.Bool("generate-signature", false, "Active l'option de génération du token de signature pour l'utilisateur.") + +func GetFlags() Flags { + if !flag.Parsed() { + flag.Parse() + } + + return Flags{ + port, + host, + portEnv, + hostEnv, + generateSignature, + } +} diff --git a/getFileSystem.go b/getFileSystem.go deleted file mode 100644 index c8c075e..0000000 --- a/getFileSystem.go +++ /dev/null @@ -1,40 +0,0 @@ -package main - -import ( - "encoding/json" - fs "filesystem_service/customFs" - "net/http" - "strings" -) - -func getFileSystem(writer http.ResponseWriter, request *http.Request) { - writer.Header().Set("Access-Control-Allow-Origin", "*") - writer.Header().Add("Content-Type", "application/json") - - path := "/" + strings.Replace(request.PathValue("path"), "%2F", "/", -1) - - d := fs.NewDirectory(path) - list, err := d.GetFlatContent() - - if err != nil { - if strings.Contains(err.Error(), "no such file or directory") { - response, _ := json.Marshal(&HttpError{ - Code: 404, - Message: err.Error(), - }) - _, _ = writer.Write(response) - return - } - - response, _ := json.Marshal(&HttpError{ - Code: 400, - Message: err.Error(), - }) - writer.WriteHeader(400) - _, _ = writer.Write(response) - return - } - - response, _ := json.Marshal(list) - _, _ = writer.Write(response) -} diff --git a/go.mod b/go.mod index 93b066d..372d3d4 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,22 @@ module filesystem_service go 1.22.0 + +require ( + github.com/atotto/clipboard v0.1.4 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + golang.org/x/sys v0.16.0 // indirect + modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect + modernc.org/libc v1.41.0 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.7.2 // indirect + modernc.org/sqlite v1.29.5 // indirect + modernc.org/strutil v1.2.0 // indirect + modernc.org/token v1.1.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a2e3d9d --- /dev/null +++ b/go.sum @@ -0,0 +1,33 @@ +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= +modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= +modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk= +modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= +modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/sqlite v1.29.5 h1:8l/SQKAjDtZFo9lkJLdk8g9JEOeYRG4/ghStDCCTiTE= +modernc.org/sqlite v1.29.5/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/main.go b/main.go index ba60786..54d5666 100644 --- a/main.go +++ b/main.go @@ -1,70 +1,55 @@ package main import ( - "flag" + "filesystem_service/actions" + "filesystem_service/auth" + "filesystem_service/customHttp" + "filesystem_service/directories" + "filesystem_service/files" "fmt" "net/http" "os" - "strconv" - "strings" -) - -type HttpError struct { - Code int `json:"code"` - Message string `json:"message"` -} - -type ResponseStatus struct { - Status string `json:"status"` -} + "os/signal" + "syscall" -func getAddr(host *string, port *int, hostEnv *string, portEnv *string) string { - if hostEnv != nil { - if envHost := os.Getenv(*hostEnv); envHost != "" { - *host = envHost - } - } - if portEnv != nil { - if envPort := os.Getenv(*portEnv); envPort != "" { - *port, _ = strconv.Atoi(envPort) - } - } - - if host != nil && strings.Contains(*host, ":") { - *host = fmt.Sprintf("[%s]", *host) - } - - return *host + ":" + strconv.Itoa(*port) -} + _ "modernc.org/sqlite" +) func main() { - port := flag.Int("port", 3000, "Port d'exposition de l'application.") - host := flag.String("host", "127.0.0.1", "Domaine ou IP de la machine qui expose le sercice.") + if !actions.Exec() { + addr := customHttp.GetAddr() - portEnvVar := flag.String("portEnv", "", "Variable d'environement où trouver le port d'exposition de l'application.") - hostEnvVar := flag.String("hostEnv", "", "Variable d'environement où trouver le domaine ou l'IP de la machine qui expose le sercice.") - flag.Parse() + server := http.NewServeMux() - addr := getAddr(host, port, hostEnvVar, portEnvVar) + server.HandleFunc("/check-validity", CheckValidity) // ok - server := http.NewServeMux() + server.HandleFunc("POST /auth/get-token", auth.GetToken) + server.HandleFunc("PUT /auth/get-token", auth.RefreshToken) - server.HandleFunc("/check-validity", checkValidity) // ok + server.HandleFunc("/file-system/{path...}", directories.GetFileSystem) // ok - server.HandleFunc("/file-system/{path...}", getFileSystem) // ok + server.HandleFunc("POST /directory", directories.CreateDirectory) // ok + server.HandleFunc("PATCH /directory/{path...}", directories.RenameDirectory) // ok + server.HandleFunc("DELETE /directory/{path...}", directories.DeleteDirectory) // ok - server.HandleFunc("POST /directory", createDirectory) // ok - server.HandleFunc("PATCH /directory/{path...}", renameDirectory) // ok - server.HandleFunc("DELETE /directory/{path...}", deleteDirectory) // ok + server.HandleFunc("/file/{path...}", files.GetFileContent) // ok + server.HandleFunc("POST /file", files.CreateFile) // ok + server.HandleFunc("PATCH /file/{path...}", files.RenameFile) // ok + server.HandleFunc("PUT /file/{path...}", files.UpdateFileContent) // ok + server.HandleFunc("DELETE /file/{path...}", files.DeleteFile) // ok - server.HandleFunc("/file/{path...}", getFileContent) // ok - server.HandleFunc("POST /file", createFile) // ok - server.HandleFunc("PATCH /file/{path...}", renameFile) // ok - server.HandleFunc("PUT /file/{path...}", updateFileContent) // ok - server.HandleFunc("DELETE /file/{path...}", deleteFile) // ok + c := make(chan os.Signal) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + <-c + println("\b\bNous fermons le serveur.") + // Run Cleanup + os.Exit(0) + }() - fmt.Printf("Listening on http://%s", addr) - if err := http.ListenAndServe(addr, server); err != nil { - println(err) + fmt.Printf("Listening on http://%s\n", addr) + if err := http.ListenAndServe(addr, server); err != nil { + println(err) + } } } diff --git a/types/checkValidity.go b/types/checkValidity.go new file mode 100644 index 0000000..ac06dcb --- /dev/null +++ b/types/checkValidity.go @@ -0,0 +1,5 @@ +package types + +type CheckValidity struct { + IsValid bool `json:"isValid"` +} diff --git a/types/httpError.go b/types/httpError.go new file mode 100644 index 0000000..b226735 --- /dev/null +++ b/types/httpError.go @@ -0,0 +1,6 @@ +package types + +type HttpError struct { + Code int `json:"code"` + Message string `json:"message"` +} diff --git a/types/responseStatus.go b/types/responseStatus.go new file mode 100644 index 0000000..18fbae0 --- /dev/null +++ b/types/responseStatus.go @@ -0,0 +1,5 @@ +package types + +type ResponseStatus struct { + Status string `json:"status"` +}