blob: ab0ea7696204331f9071b0ad2bd4a21da7cea891 [file] [log] [blame]
Giorgi Lekveishvilifedd0062023-12-21 10:52:49 +04001package main
2
3import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "net/http"
8
9 "github.com/gorilla/mux"
10)
11
12type APIServer struct {
13 r *mux.Router
14 serv *http.Server
15 kratosAddr string
16}
17
DTabidze52593392024-03-08 12:53:20 +040018type ErrorResponse struct {
19 Error struct {
20 Code int `json:"code"`
21 Status string `json:"status"`
22 Message string `json:"message"`
23 } `json:"error"`
24}
25
Giorgi Lekveishvilifedd0062023-12-21 10:52:49 +040026func NewAPIServer(port int, kratosAddr string) *APIServer {
27 r := mux.NewRouter()
28 serv := &http.Server{
29 Addr: fmt.Sprintf(":%d", port),
30 Handler: r,
31 }
32 return &APIServer{r, serv, kratosAddr}
33}
34
35func (s *APIServer) Start() error {
36 s.r.Path("/identities").Methods(http.MethodPost).HandlerFunc(s.identityCreate)
37 return s.serv.ListenAndServe()
38}
39
40const identityCreateTmpl = `
41{
42 "credentials": {
43 "password": {
44 "config": {
45 "password": "%s"
46 }
47 }
48 },
49 "schema_id": "user",
50 "state": "active",
51 "traits": {
52 "username": "%s"
53 }
54}
55`
56
57type identityCreateReq struct {
58 Username string `json:"username,omitempty"`
59 Password string `json:"password,omitempty"`
60}
61
DTabidze52593392024-03-08 12:53:20 +040062func extractKratosErrorMessage(errResp ErrorResponse) []ValidationError {
63 var errors []ValidationError
64 switch errResp.Error.Status {
65 case "Conflict":
66 errors = append(errors, ValidationError{"username", "Username is not available."})
67 case "Bad Request":
68 errors = append(errors, ValidationError{"username", "Username is less than 3 characters."})
69 default:
70 errors = append(errors, ValidationError{"username", "Unexpexted Error."})
71 }
72 return errors
73}
74
75type ValidationError struct {
76 Field string `json:"field"`
77 Message string `json:"message"`
78}
79
80type CombinedErrors struct {
81 Errors []ValidationError `json:"errors"`
82}
83
84func validateUsername(username string) []ValidationError {
85 var errors []ValidationError
86 if len(username) < 3 {
87 errors = append(errors, ValidationError{"username", "Username must be at least 3 characters long."})
88 }
89 // TODO other validations
90 return errors
91}
92
93func validatePassword(password string) []ValidationError {
94 var errors []ValidationError
95 if len(password) < 6 {
96 errors = append(errors, ValidationError{"password", "Password must be at least 6 characters long."})
97 }
98 // TODO other validations
99 return errors
100}
101
102func replyWithErrors(w http.ResponseWriter, errors []ValidationError) {
103 response := CombinedErrors{Errors: errors}
104 w.Header().Set("Content-Type", "application/json")
105 w.WriteHeader(http.StatusBadRequest)
106 if err := json.NewEncoder(w).Encode(response); err != nil {
107 http.Error(w, "failed to decode", http.StatusInternalServerError)
108 return
109 }
110}
111
Giorgi Lekveishvilifedd0062023-12-21 10:52:49 +0400112func (s *APIServer) identityCreate(w http.ResponseWriter, r *http.Request) {
113 var req identityCreateReq
114 if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
115 http.Error(w, "request can not be parsed", http.StatusBadRequest)
116 return
117 }
DTabidze52593392024-03-08 12:53:20 +0400118 usernameErrors := validateUsername(req.Username)
119 passwordErrors := validatePassword(req.Password)
120 allErrors := append(usernameErrors, passwordErrors...)
121 if len(allErrors) > 0 {
122 replyWithErrors(w, allErrors)
123 return
124 }
Giorgi Lekveishvilifedd0062023-12-21 10:52:49 +0400125 var buf bytes.Buffer
126 fmt.Fprintf(&buf, identityCreateTmpl, req.Password, req.Username)
127 resp, err := http.Post(s.identitiesEndpoint(), "application/json", &buf)
Giorgi Lekveishvili83399052024-02-14 13:27:30 +0400128 if err != nil {
Giorgi Lekveishvilifedd0062023-12-21 10:52:49 +0400129 http.Error(w, "failed", http.StatusInternalServerError)
130 return
DTabidze52593392024-03-08 12:53:20 +0400131 }
132 if resp.StatusCode != http.StatusCreated {
133 var e ErrorResponse
134 if err := json.NewDecoder(resp.Body).Decode(&e); err != nil {
135 http.Error(w, "failed to decode", http.StatusInternalServerError)
136 return
Giorgi Lekveishvili83399052024-02-14 13:27:30 +0400137 }
DTabidze52593392024-03-08 12:53:20 +0400138 errorMessages := extractKratosErrorMessage(e)
139 replyWithErrors(w, errorMessages)
140 return
Giorgi Lekveishvilifedd0062023-12-21 10:52:49 +0400141 }
142}
143
144func (s *APIServer) identitiesEndpoint() string {
145 return fmt.Sprintf("%s/admin/identities", s.kratosAddr)
146}