1
package api
2

3
import (
4
    "bytes"
5
    "encoding/base64"
6
    "fmt"
7
    "image/png"
8
    "net/http"
9

10
    "github.com/gin-gonic/gin"
11

12
    "github.com/earaujoassis/space/config"
13
    "github.com/earaujoassis/space/datastore"
14
    "github.com/earaujoassis/space/feature"
15
    "github.com/earaujoassis/space/models"
16
    "github.com/earaujoassis/space/oauth"
17
    "github.com/earaujoassis/space/security"
18
    "github.com/earaujoassis/space/services"
19
    "github.com/earaujoassis/space/services/logger"
20
    "github.com/earaujoassis/space/utils"
21
)
22

23
// exposeUsersRoutes defines and exposes HTTP routes for a given gin.RouterGroup
24
//      in the REST API escope, for the users resource
25 0
func exposeUsersRoutes(router *gin.RouterGroup) {
26 0
    usersRoutes := router.Group("/users")
27
    {
28
        // Requires X-Requested-By and Origin (same-origin policy)
29 0
        usersRoutes.POST("/create", requiresConformance, func(c *gin.Context) {
30 0
            var buf bytes.Buffer
31 0
            var imageData string
32

33 0
            if !feature.IsActive("user.create") {
34 0
                c.JSON(http.StatusForbidden, utils.H{
35 0
                    "_status":  "error",
36 0
                    "_message": "User was not created",
37 0
                    "error":    "feature is not available at this time",
38 0
                })
39 0
                return
40
            }
41

42 0
            dataStore := datastore.GetDataStoreConnection()
43 0
            user := models.User{
44 0
                FirstName:  c.PostForm("first_name"),
45 0
                LastName:   c.PostForm("last_name"),
46 0
                Username:   c.PostForm("username"),
47 0
                Email:      c.PostForm("email"),
48 0
                Passphrase: c.PostForm("password"),
49
            }
50 0
            if !models.IsValid("essential", user) {
51 0
                c.JSON(http.StatusBadRequest, utils.H{
52 0
                    "_status":  "error",
53 0
                    "_message": "User was not created",
54 0
                    "error":    "missing essential fields",
55 0
                    "user":     user,
56 0
                })
57 0
                return
58
            }
59 0
            if dataStore.NewRecord(user.Client) {
60 0
                user.Client = services.FindOrCreateClient(services.DefaultClient)
61
            }
62 0
            if dataStore.NewRecord(user.Language) {
63 0
                user.Language = services.FindOrCreateLanguage("English", "en-US")
64
            }
65 0
            codeSecretKey := user.GenerateCodeSecret()
66 0
            recoverSecret, _ := user.GenerateRecoverSecret()
67 0
            img, err := codeSecretKey.Image(200, 200)
68 0
            if err != nil {
69 0
                imageData = ""
70 0
            } else {
71 0
                png.Encode(&buf, img)
72 0
                imageData = base64.StdEncoding.EncodeToString(buf.Bytes())
73
            }
74

75 0
            result := dataStore.Create(&user)
76 0
            if count := result.RowsAffected; count < 1 {
77 0
                c.JSON(http.StatusBadRequest, utils.H{
78 0
                    "_status":  "error",
79 0
                    "_message": "User was not created",
80 0
                    "error": fmt.Sprintf("%v", result.GetErrors()),
81 0
                    "user":  user,
82 0
                })
83 0
            } else {
84 0
                go logger.LogAction("user.created", utils.H{
85 0
                    "Email":     user.Email,
86 0
                    "FirstName": user.FirstName,
87 0
                })
88 0
                c.JSON(http.StatusOK, utils.H{
89 0
                    "_status":           "created",
90 0
                    "_message":          "User was created",
91 0
                    "recover_secret":    recoverSecret,
92 0
                    "code_secret_image": imageData,
93 0
                    "user":              user,
94 0
                })
95
            }
96
        })
97

98
        // Requires X-Requested-By and Origin (same-origin policy)
99
        // Authorization type: action token / Bearer (for web use)
100 0
        usersRoutes.PATCH("/update/adminify", requiresConformance, actionTokenBearerAuthorization, func(c *gin.Context) {
101 0
            var cfg config.Config = config.GetGlobalConfig()
102 0
            var uuid = c.PostForm("user_id")
103 0
            var providedApplicationKey = c.PostForm("application_key")
104

105 0
            if !feature.IsActive("user.adminify") {
106 0
                c.JSON(http.StatusForbidden, utils.H{
107 0
                    "_status":  "error",
108 0
                    "_message": "User was not updated",
109 0
                    "error":    "feature is not available at this time",
110 0
                })
111 0
                return
112
            }
113

114 0
            if providedApplicationKey != cfg.ApplicationKey {
115 0
                c.JSON(http.StatusForbidden, utils.H{
116 0
                    "_status":  "error",
117 0
                    "_message": "User was not updated",
118 0
                    "error":    "application key is incorrect",
119 0
                })
120 0
                return
121
            }
122

123 0
            if !security.ValidUUID(uuid) {
124 0
                c.JSON(http.StatusBadRequest, utils.H{
125 0
                    "_status":  "error",
126 0
                    "_message": "User was not updated",
127 0
                    "error": "must use valid UUID for identification",
128 0
                })
129 0
                return
130
            }
131

132 0
            action := c.MustGet("Action").(models.Action)
133 0
            user := services.FindUserByUUID(uuid)
134 0
            if user.ID == 0 || user.ID != action.UserID {
135 0
                c.Header("WWW-Authenticate", fmt.Sprintf("Bearer realm=\"%s\"", c.Request.RequestURI))
136 0
                c.JSON(http.StatusUnauthorized, utils.H{
137 0
                    "_status":  "error",
138 0
                    "_message": "User was not updated",
139 0
                    "error": oauth.AccessDenied,
140 0
                })
141 0
                return
142
            }
143

144 0
            dataStore := datastore.GetDataStoreConnection()
145 0
            user.Admin = true
146 0
            dataStore.Save(&user)
147 0
            c.JSON(http.StatusNoContent, nil)
148
        })
149

150
        // Requires X-Requested-By and Origin (same-origin policy)
151 0
        usersRoutes.PATCH("/update/password", requiresConformance, func(c *gin.Context) {
152 0
            var bearer = c.PostForm("_")
153 0
            var newPassword = c.PostForm("new_password")
154 0
            var passwordConfirmation = c.PostForm("password_confirmation")
155

156 0
            if !security.ValidRandomString(bearer) {
157 0
                c.JSON(http.StatusBadRequest, utils.H{
158 0
                    "_status":  "error",
159 0
                    "_message": "User password was not updated",
160 0
                    "error": "must use valid token string",
161 0
                })
162 0
                return
163
            }
164

165 0
            action := services.ActionAuthentication(bearer)
166 0
            if action.UUID == "" || !services.ActionGrantsWriteAbility(action) || !action.CanUpdateUser() {
167 0
                c.JSON(http.StatusUnauthorized, utils.H{
168 0
                    "_status":  "error",
169 0
                    "_message": "User password was not updated",
170 0
                    "error": "token string not valid",
171 0
                })
172 0
                return
173
            }
174

175 0
            user := services.FindUserByID(action.UserID)
176 0
            if user.ID == 0 {
177 0
                c.JSON(http.StatusUnauthorized, utils.H{
178 0
                    "_status":  "error",
179 0
                    "_message": "User password was not updated",
180 0
                    "error": "token string not valid",
181 0
                })
182 0
                return
183
            }
184

185 0
            if newPassword != passwordConfirmation {
186 0
                c.JSON(http.StatusBadRequest, utils.H{
187 0
                    "_status":  "error",
188 0
                    "_message": "User password was not updated",
189 0
                    "error": "new password and password confirmation must match each other",
190 0
                })
191 0
                return
192
            }
193

194 0
            user.UpdatePassword(newPassword)
195 0
            if !models.IsValid("essential", user) {
196 0
                c.JSON(http.StatusBadRequest, utils.H{
197 0
                    "_status":  "error",
198 0
                    "_message": "User password was not updated",
199 0
                    "error":    "invalid password update attempt",
200 0
                    "user":     user,
201 0
                })
202 0
                return
203
            }
204

205 0
            dataStore := datastore.GetDataStoreConnection()
206 0
            dataStore.Save(&user)
207 0
            action.Delete()
208 0
            c.JSON(http.StatusNoContent, nil)
209
        })
210

211
        // Requires X-Requested-By and Origin (same-origin policy)
212 0
        usersRoutes.POST("/update/request", requiresConformance, func(c *gin.Context) {
213 0
            var holder = c.PostForm("holder")
214 0
            var requestType = c.PostForm("request_type")
215 0
            var host = fmt.Sprintf("%s://%s", scheme(c.Request), c.Request.Host)
216

217 0
            const (
218 0
                passwordType = "password"
219 0
                secretsType = "secrets"
220 0
            )
221

222 0
            if !security.ValidEmail(holder) && !security.ValidRandomString(holder) {
223 0
                c.JSON(http.StatusBadRequest, utils.H{
224 0
                    "_status":  "error",
225 0
                    "_message": "update request was not created",
226 0
                    "error": "must use valid holder string",
227 0
                })
228 0
                return
229
            }
230

231 0
            switch requestType {
232 0
            case passwordType:
233 0
                user := services.FindUserByAccountHolder(holder)
234 0
                client := services.FindOrCreateClient(services.DefaultClient)
235 0
                if user.ID != 0 {
236 0
                    actionToken := services.CreateAction(user, client,
237 0
                        c.Request.RemoteAddr,
238 0
                        c.Request.UserAgent(),
239 0
                        models.ReadWriteScope,
240 0
                        models.UpdateUserAction,
241 0
                    )
242 0
                    go logger.LogAction("session.magic", utils.H{
243 0
                        "Email":     user.Email,
244 0
                        "FirstName": user.FirstName,
245 0
                        "Callback": fmt.Sprintf("%s/profile/password?_=%s", host, actionToken.Token),
246 0
                    })
247
                }
248 0
            case secretsType:
249 0
                user := services.FindUserByAccountHolder(holder)
250 0
                client := services.FindOrCreateClient(services.DefaultClient)
251 0
                if user.ID != 0 {
252 0
                    actionToken := services.CreateAction(user, client,
253 0
                        c.Request.RemoteAddr,
254 0
                        c.Request.UserAgent(),
255 0
                        models.ReadWriteScope,
256 0
                        models.UpdateUserAction,
257 0
                    )
258 0
                    go logger.LogAction("session.magic", utils.H{
259 0
                        "Email":     user.Email,
260 0
                        "FirstName": user.FirstName,
261 0
                        "Callback": fmt.Sprintf("%s/profile/secrets?_=%s", host, actionToken.Token),
262 0
                    })
263
                }
264 0
            default:
265 0
                c.JSON(http.StatusBadRequest, utils.H{
266 0
                    "_status":  "error",
267 0
                    "_message": "update request was not created",
268 0
                    "error": "request type not available",
269 0
                })
270 0
                return
271
            }
272

273
            // No Content is the default response
274 0
            c.JSON(http.StatusNoContent, nil)
275
        })
276

277
        // Authorization type: access session / Bearer (for OAuth sessions)
278 0
        usersRoutes.POST("/introspect", oAuthTokenBearerAuthorization, func(c *gin.Context) {
279 0
            var publicID = c.PostForm("user_id")
280

281 0
            if !security.ValidRandomString(publicID) {
282 0
                c.JSON(http.StatusBadRequest, utils.H{
283 0
                    "_status":  "error",
284 0
                    "_message": "User instropection failed",
285 0
                    "error": "must use valid identification string",
286 0
                })
287 0
                return
288
            }
289 0
            session := c.MustGet("Session").(models.Session)
290 0
            user := services.FindUserByPublicID(publicID)
291 0
            if user.ID == 0 || user.ID != session.UserID {
292 0
                c.Header("WWW-Authenticate", fmt.Sprintf("Bearer realm=\"%s\"", c.Request.RequestURI))
293 0
                c.JSON(http.StatusUnauthorized, utils.H{
294 0
                    "_status":  "error",
295 0
                    "_message": "User instropection failed",
296 0
                    "error": oauth.AccessDenied,
297 0
                })
298 0
                return
299
            }
300

301 0
            c.JSON(http.StatusOK, utils.H{
302 0
                "_status":  "success",
303 0
                "_message": "User instropection fulfilled",
304 0
                "user": user,
305 0
            })
306
        })
307

308
        // Requires X-Requested-By and Origin (same-origin policy)
309
        // Authorization type: action token / Bearer (for web use)
310 0
        usersRoutes.GET("/:user_id/profile", requiresConformance, actionTokenBearerAuthorization, func(c *gin.Context) {
311 0
            var uuid = c.Param("user_id")
312

313 0
            if !security.ValidUUID(uuid) {
314 0
                c.JSON(http.StatusBadRequest, utils.H{
315 0
                    "_status":  "error",
316 0
                    "_message": "User instropection failed",
317 0
                    "error": "must use valid UUID for identification",
318 0
                })
319 0
                return
320
            }
321

322 0
            action := c.MustGet("Action").(models.Action)
323 0
            user := services.FindUserByUUID(uuid)
324 0
            if user.ID == 0 || user.ID != action.UserID {
325 0
                c.Header("WWW-Authenticate", fmt.Sprintf("Bearer realm=\"%s\"", c.Request.RequestURI))
326 0
                c.JSON(http.StatusUnauthorized, utils.H{
327 0
                    "_status":  "error",
328 0
                    "_message": "User instropection failed",
329 0
                    "error": oauth.AccessDenied,
330 0
                })
331 0
                return
332
            }
333

334 0
            c.JSON(http.StatusOK, utils.H{
335 0
                "_status":  "success",
336 0
                "_message": "User instropection fulfilled",
337 0
                "user": utils.H{
338 0
                    "is_admin":            user.Admin,
339 0
                    "username":            user.Username,
340 0
                    "first_name":          user.FirstName,
341 0
                    "last_name":           user.LastName,
342 0
                    "email":               user.Email,
343 0
                    "timezone_identifier": user.TimezoneIdentifier,
344 0
                },
345 0
            })
346
        })
347

348
        // Requires X-Requested-By and Origin (same-origin policy)
349
        // Authorization type: action token / Bearer (for web use)
350 0
        usersRoutes.DELETE("/:user_id/deactivate", requiresConformance, actionTokenBearerAuthorization, func(c *gin.Context) {
351 0
            c.String(http.StatusMethodNotAllowed, "Not implemented")
352 0
        })
353

354
        // Requires X-Requested-By and Origin (same-origin policy)
355
        // Authorization type: action token / Bearer (for web use)
356 0
        usersRoutes.GET("/:user_id/clients", requiresConformance, actionTokenBearerAuthorization, func(c *gin.Context) {
357 0
            var uuid = c.Param("user_id")
358

359 0
            if !security.ValidUUID(uuid) {
360 0
                c.JSON(http.StatusBadRequest, utils.H{
361 0
                    "_status":  "error",
362 0
                    "_message": "User's clients unavailable",
363 0
                    "error": "must use valid UUID for identification",
364 0
                })
365 0
                return
366
            }
367

368 0
            action := c.MustGet("Action").(models.Action)
369 0
            user := services.FindUserByUUID(uuid)
370 0
            if user.ID == 0 || user.ID != action.UserID {
371 0
                c.Header("WWW-Authenticate", fmt.Sprintf("Bearer realm=\"%s\"", c.Request.RequestURI))
372 0
                c.JSON(http.StatusUnauthorized, utils.H{
373 0
                    "_status":  "error",
374 0
                    "_message": "User's clients unavailable",
375 0
                    "error": oauth.AccessDenied,
376 0
                })
377 0
                return
378
            }
379

380 0
            c.JSON(http.StatusOK, utils.H{
381 0
                "_status":  "success",
382 0
                "_message": "User's clients available",
383 0
                "clients": services.ActiveClientsForUser(user.ID),
384 0
            })
385
        })
386

387
        // Requires X-Requested-By and Origin (same-origin policy)
388
        // Authorization type: action token / Bearer (for web use)
389 0
        usersRoutes.DELETE("/:user_id/clients/:client_id/revoke", requiresConformance, actionTokenBearerAuthorization, func(c *gin.Context) {
390 0
            var userUUID = c.Param("user_id")
391 0
            var clientUUID = c.Param("client_id")
392

393 0
            if !security.ValidUUID(userUUID) || !security.ValidUUID(clientUUID) {
394 0
                c.JSON(http.StatusBadRequest, utils.H{
395 0
                    "_status":  "error",
396 0
                    "_message": "Client application irrevocable",
397 0
                    "error": "must use valid UUID for identification",
398 0
                })
399 0
                return
400
            }
401

402 0
            action := c.MustGet("Action").(models.Action)
403 0
            user := services.FindUserByUUID(userUUID)
404 0
            if user.ID == 0 || user.ID != action.UserID {
405 0
                c.Header("WWW-Authenticate", fmt.Sprintf("Bearer realm=\"%s\"", c.Request.RequestURI))
406 0
                c.JSON(http.StatusUnauthorized, utils.H{
407 0
                    "_status":  "error",
408 0
                    "_message": "Client application irrevocable",
409 0
                    "error": oauth.AccessDenied,
410 0
                })
411 0
                return
412
            }
413

414 0
            client := services.FindClientByUUID(clientUUID)
415 0
            services.RevokeClientAccess(client.ID, user.ID)
416 0
            c.JSON(http.StatusNoContent, nil)
417
        })
418
    }
419
}

Read our documentation on viewing source code .

Loading