server.go 8.41 KB
Newer Older
ale's avatar
ale committed
1
2
3
package server

import (
ale's avatar
ale committed
4
5
	"context"
	"encoding/json"
6
	"errors"
ale's avatar
ale committed
7
8
9
10
11
	"log"
	"net/http"

	"git.autistici.org/ai3/go-common/serverutil"

ale's avatar
ale committed
12
	as "git.autistici.org/ai3/accountserver"
ale's avatar
ale committed
13
14
)

ale's avatar
ale committed
15
type txHandler func(as.TX, http.ResponseWriter, *http.Request) (interface{}, error)
16
17
18

var errBadRequest = errors.New("bad request")

ale's avatar
ale committed
19
20
type AccountServer struct {
	service *as.AccountService
21
	backend as.Backend
ale's avatar
ale committed
22
23
}

24
25
26
27
28
func New(service *as.AccountService, backend as.Backend) *AccountServer {
	return &AccountServer{
		service: service,
		backend: backend,
	}
ale's avatar
ale committed
29
30
}

ale's avatar
ale committed
31
var emptyResponse struct{}
ale's avatar
ale committed
32

ale's avatar
ale committed
33
func (s *AccountServer) handleGetUser(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
ale's avatar
ale committed
34
	var req as.GetUserRequest
ale's avatar
ale committed
35
36
37
	return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
		return s.service.GetUser(ctx, tx, &req)
	})
38
39
}

40
41
42
43
44
45
46
func (s *AccountServer) handleCreateUser(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
	var req as.CreateUserRequest
	return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
		return s.service.CreateUser(ctx, tx, &req)
	})
}

47
48
49
50
51
52
53
func (s *AccountServer) handleUpdateUser(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
	var req as.UpdateUserRequest
	return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
		return s.service.UpdateUser(ctx, tx, &req)
	})
}

ale's avatar
ale committed
54
func (s *AccountServer) handleChangeUserPassword(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
ale's avatar
ale committed
55
	var req as.ChangeUserPasswordRequest
ale's avatar
ale committed
56
57
58
	return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
		return &emptyResponse, s.service.ChangeUserPassword(ctx, tx, &req)
	})
59
60
}

ale's avatar
ale committed
61
62
63
64
65
66
67
68
69
70
func (s *AccountServer) handleSetPasswordRecoveryHint(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
	var req as.SetPasswordRecoveryHintRequest
	return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
		return &emptyResponse, s.service.SetPasswordRecoveryHint(ctx, tx, &req)
	})
}

func (s *AccountServer) handleRecoverPassword(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
	var req as.PasswordRecoveryRequest
	return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
71
		return s.service.RecoverPassword(ctx, tx, &req)
ale's avatar
ale committed
72
73
74
	})
}

ale's avatar
ale committed
75
func (s *AccountServer) handleCreateApplicationSpecificPassword(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
ale's avatar
ale committed
76
	var req as.CreateApplicationSpecificPasswordRequest
ale's avatar
ale committed
77
78
79
	return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
		return s.service.CreateApplicationSpecificPassword(ctx, tx, &req)
	})
ale's avatar
ale committed
80
81
}

ale's avatar
ale committed
82
func (s *AccountServer) handleDeleteApplicationSpecificPassword(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
ale's avatar
ale committed
83
	var req as.DeleteApplicationSpecificPasswordRequest
ale's avatar
ale committed
84
85
86
	return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
		return &emptyResponse, s.service.DeleteApplicationSpecificPassword(ctx, tx, &req)
	})
ale's avatar
ale committed
87
88
}

ale's avatar
ale committed
89
func (s *AccountServer) handleEnableResource(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
ale's avatar
ale committed
90
	var req as.EnableResourceRequest
ale's avatar
ale committed
91
92
93
	return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
		return &emptyResponse, s.service.EnableResource(ctx, tx, &req)
	})
ale's avatar
ale committed
94
95
}

ale's avatar
ale committed
96
func (s *AccountServer) handleDisableResource(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
ale's avatar
ale committed
97
	var req as.DisableResourceRequest
ale's avatar
ale committed
98
99
100
	return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
		return &emptyResponse, s.service.DisableResource(ctx, tx, &req)
	})
ale's avatar
ale committed
101
102
}

ale's avatar
ale committed
103
104
105
106
107
108
109
func (s *AccountServer) handleCreateResources(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
	var req as.CreateResourcesRequest
	return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
		return s.service.CreateResources(ctx, tx, &req)
	})
}

ale's avatar
ale committed
110
func (s *AccountServer) handleMoveResource(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
ale's avatar
ale committed
111
	var req as.MoveResourceRequest
ale's avatar
ale committed
112
113
114
	return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
		return s.service.MoveResource(ctx, tx, &req)
	})
ale's avatar
ale committed
115
116
}

117
118
119
120
121
122
123
func (s *AccountServer) handleResetResourcePassword(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
	var req as.ResetResourcePasswordRequest
	return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
		return s.service.ResetResourcePassword(ctx, tx, &req)
	})
}

ale's avatar
ale committed
124
func (s *AccountServer) handleEnableOTP(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
ale's avatar
ale committed
125
	var req as.EnableOTPRequest
ale's avatar
ale committed
126
127
128
	return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
		return s.service.EnableOTP(ctx, tx, &req)
	})
ale's avatar
ale committed
129
130
}

ale's avatar
ale committed
131
func (s *AccountServer) handleDisableOTP(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
ale's avatar
ale committed
132
	var req as.DisableOTPRequest
ale's avatar
ale committed
133
134
135
	return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
		return &emptyResponse, s.service.DisableOTP(ctx, tx, &req)
	})
136
137
}

138
139
140
141
142
143
144
145
146
147
148
149
150
151
func (s *AccountServer) handleAddEmailAlias(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
	var req as.AddEmailAliasRequest
	return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
		return &emptyResponse, s.service.AddEmailAlias(ctx, tx, &req)
	})
}

func (s *AccountServer) handleDeleteEmailAlias(tx as.TX, w http.ResponseWriter, r *http.Request) (interface{}, error) {
	var req as.DeleteEmailAliasRequest
	return handleJSON(w, r, &req, func(ctx context.Context) (interface{}, error) {
		return &emptyResponse, s.service.DeleteEmailAlias(ctx, tx, &req)
	})
}

152
153
154
155
156
157
158
159
160
func (s *AccountServer) withTx(f txHandler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		tx, err := s.backend.NewTransaction()
		if err != nil {
			log.Printf("NewTransaction error: %v", err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

ale's avatar
ale committed
161
162
163
164
165
166
167
168
169
170
		resp, err := f(tx, w, r)
		if err != nil {
			return
		}
		// Automatically commit the transaction (if
		// the handler didn't do this itself).
		if err := tx.Commit(r.Context()); err != nil {
			log.Printf("Commit error: %v", err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
171
		}
ale's avatar
ale committed
172
173

		serverutil.EncodeJSONResponse(w, resp)
174
	}
ale's avatar
ale committed
175
176
}

ale's avatar
ale committed
177
178
func (s *AccountServer) Handler() http.Handler {
	h := http.NewServeMux()
179
	h.HandleFunc("/api/user/get", s.withTx(s.handleGetUser))
180
	h.HandleFunc("/api/user/create", s.withTx(s.handleCreateUser))
181
	h.HandleFunc("/api/user/update", s.withTx(s.handleUpdateUser))
182
	h.HandleFunc("/api/user/change_password", s.withTx(s.handleChangeUserPassword))
ale's avatar
ale committed
183
	h.HandleFunc("/api/user/set_password_recovery_hint", s.withTx(s.handleSetPasswordRecoveryHint))
184
185
	h.HandleFunc("/api/user/enable_otp", s.withTx(s.handleEnableOTP))
	h.HandleFunc("/api/user/disable_otp", s.withTx(s.handleDisableOTP))
ale's avatar
ale committed
186
187
	h.HandleFunc("/api/user/create_app_specific_password", s.withTx(s.handleCreateApplicationSpecificPassword))
	h.HandleFunc("/api/user/delete_app_specific_password", s.withTx(s.handleDeleteApplicationSpecificPassword))
188
189
	h.HandleFunc("/api/resource/enable", s.withTx(s.handleEnableResource))
	h.HandleFunc("/api/resource/disable", s.withTx(s.handleDisableResource))
ale's avatar
ale committed
190
	h.HandleFunc("/api/resource/create", s.withTx(s.handleCreateResources))
191
	h.HandleFunc("/api/resource/move", s.withTx(s.handleMoveResource))
192
	h.HandleFunc("/api/resource/reset_password", s.withTx(s.handleResetResourcePassword))
193
	h.HandleFunc("/api/resource/email/add_alias", s.withTx(s.handleAddEmailAlias))
194
	h.HandleFunc("/api/resource/email/delete_alias", s.withTx(s.handleDeleteEmailAlias))
ale's avatar
ale committed
195
	h.HandleFunc("/api/recover_password", s.withTx(s.handleRecoverPassword))
ale's avatar
ale committed
196
197
	return h
}
ale's avatar
ale committed
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228

func errToStatus(err error) int {
	switch {
	case err == as.ErrUserNotFound, err == as.ErrResourceNotFound:
		return http.StatusNotFound
	case as.IsAuthError(err):
		return http.StatusUnauthorized
	case as.IsRequestError(err):
		return http.StatusBadRequest
	default:
		return http.StatusInternalServerError
	}
}

func handleJSON(w http.ResponseWriter, r *http.Request, req interface{}, f func(context.Context) (interface{}, error)) (interface{}, error) {
	if !serverutil.DecodeJSONRequest(w, r, req) {
		return nil, errBadRequest
	}
	resp, err := f(r.Context())
	if err != nil {
		log.Printf("error in %s: %v, request=%s", r.URL.Path, err, dumpRequest(req))
		http.Error(w, err.Error(), errToStatus(err))
		return nil, err
	}
	return resp, nil
}

func dumpRequest(req interface{}) string {
	data, _ := json.Marshal(req)
	return string(data)
}