code.gitea.io/gitea@v1.22.3/routers/api/v1/user/app.go (about) 1 // Copyright 2014 The Gogs Authors. All rights reserved. 2 // Copyright 2018 The Gitea Authors. All rights reserved. 3 // SPDX-License-Identifier: MIT 4 5 package user 6 7 import ( 8 "errors" 9 "fmt" 10 "net/http" 11 "strconv" 12 "strings" 13 14 auth_model "code.gitea.io/gitea/models/auth" 15 "code.gitea.io/gitea/models/db" 16 api "code.gitea.io/gitea/modules/structs" 17 "code.gitea.io/gitea/modules/web" 18 "code.gitea.io/gitea/routers/api/v1/utils" 19 "code.gitea.io/gitea/services/context" 20 "code.gitea.io/gitea/services/convert" 21 ) 22 23 // ListAccessTokens list all the access tokens 24 func ListAccessTokens(ctx *context.APIContext) { 25 // swagger:operation GET /users/{username}/tokens user userGetTokens 26 // --- 27 // summary: List the authenticated user's access tokens 28 // produces: 29 // - application/json 30 // parameters: 31 // - name: username 32 // in: path 33 // description: username of user 34 // type: string 35 // required: true 36 // - name: page 37 // in: query 38 // description: page number of results to return (1-based) 39 // type: integer 40 // - name: limit 41 // in: query 42 // description: page size of results 43 // type: integer 44 // responses: 45 // "200": 46 // "$ref": "#/responses/AccessTokenList" 47 // "403": 48 // "$ref": "#/responses/forbidden" 49 50 opts := auth_model.ListAccessTokensOptions{UserID: ctx.ContextUser.ID, ListOptions: utils.GetListOptions(ctx)} 51 52 tokens, count, err := db.FindAndCount[auth_model.AccessToken](ctx, opts) 53 if err != nil { 54 ctx.InternalServerError(err) 55 return 56 } 57 58 apiTokens := make([]*api.AccessToken, len(tokens)) 59 for i := range tokens { 60 apiTokens[i] = &api.AccessToken{ 61 ID: tokens[i].ID, 62 Name: tokens[i].Name, 63 TokenLastEight: tokens[i].TokenLastEight, 64 Scopes: tokens[i].Scope.StringSlice(), 65 } 66 } 67 68 ctx.SetTotalCountHeader(count) 69 ctx.JSON(http.StatusOK, &apiTokens) 70 } 71 72 // CreateAccessToken create access tokens 73 func CreateAccessToken(ctx *context.APIContext) { 74 // swagger:operation POST /users/{username}/tokens user userCreateToken 75 // --- 76 // summary: Create an access token 77 // consumes: 78 // - application/json 79 // produces: 80 // - application/json 81 // parameters: 82 // - name: username 83 // in: path 84 // description: username of user 85 // required: true 86 // type: string 87 // - name: body 88 // in: body 89 // schema: 90 // "$ref": "#/definitions/CreateAccessTokenOption" 91 // responses: 92 // "201": 93 // "$ref": "#/responses/AccessToken" 94 // "400": 95 // "$ref": "#/responses/error" 96 // "403": 97 // "$ref": "#/responses/forbidden" 98 99 form := web.GetForm(ctx).(*api.CreateAccessTokenOption) 100 101 t := &auth_model.AccessToken{ 102 UID: ctx.ContextUser.ID, 103 Name: form.Name, 104 } 105 106 exist, err := auth_model.AccessTokenByNameExists(ctx, t) 107 if err != nil { 108 ctx.InternalServerError(err) 109 return 110 } 111 if exist { 112 ctx.Error(http.StatusBadRequest, "AccessTokenByNameExists", errors.New("access token name has been used already")) 113 return 114 } 115 116 scope, err := auth_model.AccessTokenScope(strings.Join(form.Scopes, ",")).Normalize() 117 if err != nil { 118 ctx.Error(http.StatusBadRequest, "AccessTokenScope.Normalize", fmt.Errorf("invalid access token scope provided: %w", err)) 119 return 120 } 121 if scope == "" { 122 ctx.Error(http.StatusBadRequest, "AccessTokenScope", "access token must have a scope") 123 return 124 } 125 t.Scope = scope 126 127 if err := auth_model.NewAccessToken(ctx, t); err != nil { 128 ctx.Error(http.StatusInternalServerError, "NewAccessToken", err) 129 return 130 } 131 ctx.JSON(http.StatusCreated, &api.AccessToken{ 132 Name: t.Name, 133 Token: t.Token, 134 ID: t.ID, 135 TokenLastEight: t.TokenLastEight, 136 Scopes: t.Scope.StringSlice(), 137 }) 138 } 139 140 // DeleteAccessToken delete access tokens 141 func DeleteAccessToken(ctx *context.APIContext) { 142 // swagger:operation DELETE /users/{username}/tokens/{token} user userDeleteAccessToken 143 // --- 144 // summary: delete an access token 145 // produces: 146 // - application/json 147 // parameters: 148 // - name: username 149 // in: path 150 // description: username of user 151 // type: string 152 // required: true 153 // - name: token 154 // in: path 155 // description: token to be deleted, identified by ID and if not available by name 156 // type: string 157 // required: true 158 // responses: 159 // "204": 160 // "$ref": "#/responses/empty" 161 // "403": 162 // "$ref": "#/responses/forbidden" 163 // "404": 164 // "$ref": "#/responses/notFound" 165 // "422": 166 // "$ref": "#/responses/error" 167 168 token := ctx.Params(":id") 169 tokenID, _ := strconv.ParseInt(token, 0, 64) 170 171 if tokenID == 0 { 172 tokens, err := db.Find[auth_model.AccessToken](ctx, auth_model.ListAccessTokensOptions{ 173 Name: token, 174 UserID: ctx.ContextUser.ID, 175 }) 176 if err != nil { 177 ctx.Error(http.StatusInternalServerError, "ListAccessTokens", err) 178 return 179 } 180 181 switch len(tokens) { 182 case 0: 183 ctx.NotFound() 184 return 185 case 1: 186 tokenID = tokens[0].ID 187 default: 188 ctx.Error(http.StatusUnprocessableEntity, "DeleteAccessTokenByID", fmt.Errorf("multiple matches for token name '%s'", token)) 189 return 190 } 191 } 192 if tokenID == 0 { 193 ctx.Error(http.StatusInternalServerError, "Invalid TokenID", nil) 194 return 195 } 196 197 if err := auth_model.DeleteAccessTokenByID(ctx, tokenID, ctx.ContextUser.ID); err != nil { 198 if auth_model.IsErrAccessTokenNotExist(err) { 199 ctx.NotFound() 200 } else { 201 ctx.Error(http.StatusInternalServerError, "DeleteAccessTokenByID", err) 202 } 203 return 204 } 205 206 ctx.Status(http.StatusNoContent) 207 } 208 209 // CreateOauth2Application is the handler to create a new OAuth2 Application for the authenticated user 210 func CreateOauth2Application(ctx *context.APIContext) { 211 // swagger:operation POST /user/applications/oauth2 user userCreateOAuth2Application 212 // --- 213 // summary: creates a new OAuth2 application 214 // produces: 215 // - application/json 216 // parameters: 217 // - name: body 218 // in: body 219 // required: true 220 // schema: 221 // "$ref": "#/definitions/CreateOAuth2ApplicationOptions" 222 // responses: 223 // "201": 224 // "$ref": "#/responses/OAuth2Application" 225 // "400": 226 // "$ref": "#/responses/error" 227 228 data := web.GetForm(ctx).(*api.CreateOAuth2ApplicationOptions) 229 230 app, err := auth_model.CreateOAuth2Application(ctx, auth_model.CreateOAuth2ApplicationOptions{ 231 Name: data.Name, 232 UserID: ctx.Doer.ID, 233 RedirectURIs: data.RedirectURIs, 234 ConfidentialClient: data.ConfidentialClient, 235 }) 236 if err != nil { 237 ctx.Error(http.StatusBadRequest, "", "error creating oauth2 application") 238 return 239 } 240 secret, err := app.GenerateClientSecret(ctx) 241 if err != nil { 242 ctx.Error(http.StatusBadRequest, "", "error creating application secret") 243 return 244 } 245 app.ClientSecret = secret 246 247 ctx.JSON(http.StatusCreated, convert.ToOAuth2Application(app)) 248 } 249 250 // ListOauth2Applications list all the Oauth2 application 251 func ListOauth2Applications(ctx *context.APIContext) { 252 // swagger:operation GET /user/applications/oauth2 user userGetOauth2Application 253 // --- 254 // summary: List the authenticated user's oauth2 applications 255 // produces: 256 // - application/json 257 // parameters: 258 // - name: page 259 // in: query 260 // description: page number of results to return (1-based) 261 // type: integer 262 // - name: limit 263 // in: query 264 // description: page size of results 265 // type: integer 266 // responses: 267 // "200": 268 // "$ref": "#/responses/OAuth2ApplicationList" 269 270 apps, total, err := db.FindAndCount[auth_model.OAuth2Application](ctx, auth_model.FindOAuth2ApplicationsOptions{ 271 ListOptions: utils.GetListOptions(ctx), 272 OwnerID: ctx.Doer.ID, 273 }) 274 if err != nil { 275 ctx.Error(http.StatusInternalServerError, "ListOAuth2Applications", err) 276 return 277 } 278 279 apiApps := make([]*api.OAuth2Application, len(apps)) 280 for i := range apps { 281 apiApps[i] = convert.ToOAuth2Application(apps[i]) 282 apiApps[i].ClientSecret = "" // Hide secret on application list 283 } 284 285 ctx.SetTotalCountHeader(total) 286 ctx.JSON(http.StatusOK, &apiApps) 287 } 288 289 // DeleteOauth2Application delete OAuth2 Application 290 func DeleteOauth2Application(ctx *context.APIContext) { 291 // swagger:operation DELETE /user/applications/oauth2/{id} user userDeleteOAuth2Application 292 // --- 293 // summary: delete an OAuth2 Application 294 // produces: 295 // - application/json 296 // parameters: 297 // - name: id 298 // in: path 299 // description: token to be deleted 300 // type: integer 301 // format: int64 302 // required: true 303 // responses: 304 // "204": 305 // "$ref": "#/responses/empty" 306 // "404": 307 // "$ref": "#/responses/notFound" 308 appID := ctx.ParamsInt64(":id") 309 if err := auth_model.DeleteOAuth2Application(ctx, appID, ctx.Doer.ID); err != nil { 310 if auth_model.IsErrOAuthApplicationNotFound(err) { 311 ctx.NotFound() 312 } else { 313 ctx.Error(http.StatusInternalServerError, "DeleteOauth2ApplicationByID", err) 314 } 315 return 316 } 317 318 ctx.Status(http.StatusNoContent) 319 } 320 321 // GetOauth2Application get OAuth2 Application 322 func GetOauth2Application(ctx *context.APIContext) { 323 // swagger:operation GET /user/applications/oauth2/{id} user userGetOAuth2Application 324 // --- 325 // summary: get an OAuth2 Application 326 // produces: 327 // - application/json 328 // parameters: 329 // - name: id 330 // in: path 331 // description: Application ID to be found 332 // type: integer 333 // format: int64 334 // required: true 335 // responses: 336 // "200": 337 // "$ref": "#/responses/OAuth2Application" 338 // "404": 339 // "$ref": "#/responses/notFound" 340 appID := ctx.ParamsInt64(":id") 341 app, err := auth_model.GetOAuth2ApplicationByID(ctx, appID) 342 if err != nil { 343 if auth_model.IsErrOauthClientIDInvalid(err) || auth_model.IsErrOAuthApplicationNotFound(err) { 344 ctx.NotFound() 345 } else { 346 ctx.Error(http.StatusInternalServerError, "GetOauth2ApplicationByID", err) 347 } 348 return 349 } 350 if app.UID != ctx.Doer.ID { 351 ctx.NotFound() 352 return 353 } 354 355 app.ClientSecret = "" 356 357 ctx.JSON(http.StatusOK, convert.ToOAuth2Application(app)) 358 } 359 360 // UpdateOauth2Application update OAuth2 Application 361 func UpdateOauth2Application(ctx *context.APIContext) { 362 // swagger:operation PATCH /user/applications/oauth2/{id} user userUpdateOAuth2Application 363 // --- 364 // summary: update an OAuth2 Application, this includes regenerating the client secret 365 // produces: 366 // - application/json 367 // parameters: 368 // - name: id 369 // in: path 370 // description: application to be updated 371 // type: integer 372 // format: int64 373 // required: true 374 // - name: body 375 // in: body 376 // required: true 377 // schema: 378 // "$ref": "#/definitions/CreateOAuth2ApplicationOptions" 379 // responses: 380 // "200": 381 // "$ref": "#/responses/OAuth2Application" 382 // "404": 383 // "$ref": "#/responses/notFound" 384 appID := ctx.ParamsInt64(":id") 385 386 data := web.GetForm(ctx).(*api.CreateOAuth2ApplicationOptions) 387 388 app, err := auth_model.UpdateOAuth2Application(ctx, auth_model.UpdateOAuth2ApplicationOptions{ 389 Name: data.Name, 390 UserID: ctx.Doer.ID, 391 ID: appID, 392 RedirectURIs: data.RedirectURIs, 393 ConfidentialClient: data.ConfidentialClient, 394 }) 395 if err != nil { 396 if auth_model.IsErrOauthClientIDInvalid(err) || auth_model.IsErrOAuthApplicationNotFound(err) { 397 ctx.NotFound() 398 } else { 399 ctx.Error(http.StatusInternalServerError, "UpdateOauth2ApplicationByID", err) 400 } 401 return 402 } 403 app.ClientSecret, err = app.GenerateClientSecret(ctx) 404 if err != nil { 405 ctx.Error(http.StatusBadRequest, "", "error updating application secret") 406 return 407 } 408 409 ctx.JSON(http.StatusOK, convert.ToOAuth2Application(app)) 410 }