Skip to content

Commit

Permalink
Implemented creating users, ui changes for API menu
Browse files Browse the repository at this point in the history
  • Loading branch information
Forceu committed Dec 26, 2024
1 parent c93d7c6 commit fa6e000
Show file tree
Hide file tree
Showing 16 changed files with 480 additions and 82 deletions.
5 changes: 5 additions & 0 deletions internal/configuration/database/Database.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ func GetUser(id int) (models.User, bool) {
return db.GetUser(id)
}

// GetUserByEmail returns a models.User if valid or false if the email is not valid
func GetUserByEmail(email string) (models.User, bool) {
return db.GetUserByEmail(email)
}

// SaveUser saves a user to the database. If isNewUser is true, a new Id will be generated
func SaveUser(user models.User, isNewUser bool) {
db.SaveUser(user, isNewUser)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ type Database interface {
GetAllUsers() []models.User
// GetUser returns a models.User if valid or false if the ID is not valid
GetUser(id int) (models.User, bool)
// GetUserByEmail returns a models.User if valid or false if the email is not valid
GetUserByEmail(email string) (models.User, bool)
// SaveUser saves a user to the database. If isNewUser is true, a new Id will be generated
SaveUser(user models.User, isNewUser bool)
// UpdateUserLastOnline writes the last online time to the database
Expand Down
13 changes: 13 additions & 0 deletions internal/configuration/database/provider/redis/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
redigo "github.com/gomodule/redigo/redis"
"slices"
"strconv"
"strings"
"time"
)

Expand Down Expand Up @@ -47,6 +48,17 @@ func orderUsers(users []models.User) []models.User {
return users
}

// GetUserByEmail returns a models.User if valid or false if the email is not valid
func (p DatabaseProvider) GetUserByEmail(email string) (models.User, bool) {
users := p.GetAllUsers()
for _, user := range users {
if user.Email == email {
return user, true
}
}
return models.User{}, false
}

// GetUser returns a models.User if valid or false if the ID is not valid
func (p DatabaseProvider) GetUser(id int) (models.User, bool) {
result, ok := p.getHashMap(prefixUsers + strconv.Itoa(id))
Expand All @@ -60,6 +72,7 @@ func (p DatabaseProvider) GetUser(id int) (models.User, bool) {

// SaveUser saves a user to the database. If isNewUser is true, a new Id will be generated
func (p DatabaseProvider) SaveUser(user models.User, isNewUser bool) {
user.Email = strings.ToLower(user.Email)
if isNewUser {
id := p.getIncreasedInt(prefixUserIdCounter)
user.Id = id
Expand Down
24 changes: 22 additions & 2 deletions internal/configuration/database/provider/sqlite/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"github.com/forceu/gokapi/internal/helper"
"github.com/forceu/gokapi/internal/models"
"strings"
"time"
)

Expand Down Expand Up @@ -46,15 +47,34 @@ func (p DatabaseProvider) GetUser(id int) (models.User, bool) {
return result, true
}

// GetUserByEmail returns a models.User if valid or false if the email is not valid
func (p DatabaseProvider) GetUserByEmail(email string) (models.User, bool) {
var result models.User
var password sql.NullString
row := p.sqliteDb.QueryRow("SELECT * FROM Users WHERE email = ?", email)
err := row.Scan(&result.Id, &result.Email, &password, &result.Name, &result.Permissions, &result.UserLevel, &result.LastOnline)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return models.User{}, false
}
helper.Check(err)
return models.User{}, false
}
if password.Valid {
result.Password = password.String
}
return result, true
}

// SaveUser saves a user to the database. If isNewUser is true, a new Id will be generated
func (p DatabaseProvider) SaveUser(user models.User, isNewUser bool) {
if isNewUser {
_, err := p.sqliteDb.Exec("INSERT INTO Users ( Name, Email, Password, Permissions, Userlevel, LastOnline) VALUES (?, ?, ?, ?, ?, ?)",
user.Name, user.Email, user.Password, user.Permissions, user.UserLevel, user.LastOnline)
user.Name, strings.ToLower(user.Email), user.Password, user.Permissions, user.UserLevel, user.LastOnline)
helper.Check(err)
} else {
_, err := p.sqliteDb.Exec("INSERT OR REPLACE INTO Users (Id, Name, Email, Password, Permissions, Userlevel, LastOnline) VALUES (?, ?, ?, ?, ?, ?, ?)",
user.Id, user.Name, user.Email, user.Password, user.Permissions, user.UserLevel, user.LastOnline)
user.Id, user.Name, strings.ToLower(user.Email), user.Password, user.Permissions, user.UserLevel, user.LastOnline)
helper.Check(err)
}
}
Expand Down
15 changes: 13 additions & 2 deletions internal/models/User.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package models

import "time"
import (
"encoding/json"
"github.com/forceu/gokapi/internal/helper"
"time"
)

type User struct {
Id int `json:"id" redis:"id"`
Expand All @@ -9,7 +13,7 @@ type User struct {
Permissions uint16 `json:"permissions" redis:"Permissions"`
UserLevel uint8 `json:"userLevel" redis:"UserLevel"`
LastOnline int64 `json:"lastOnline" redis:"LastOnline"`
Password string `redis:"Password"`
Password string `json:"-" redis:"Password"`
}

// GetReadableDate returns the date as YYYY-MM-DD HH:MM
Expand Down Expand Up @@ -37,6 +41,13 @@ func (u *User) GetReadableUserLevel() string {
}
}

// ToJson returns the user as a JSon object
func (u *User) ToJson() string {
result, err := json.Marshal(u)
helper.Check(err)
return string(result)
}

const UserLevelSuperAdmin = 0
const UserLevelAdmin = 1
const UserLevelUser = 2
Expand Down
38 changes: 38 additions & 0 deletions internal/webserver/api/Api.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ func Process(w http.ResponseWriter, r *http.Request, maxMemory int) {
modifyApiPermission(w, request, user)
case "/auth/delete":
deleteApiKey(w, request, user)
case "/user/create":
addUser(w, request)
case "/user/changeRank":
changeUserRank(w, request)
case "/user/modify":
Expand Down Expand Up @@ -154,6 +156,8 @@ func getApiPermissionRequired(requestUrl string) (uint8, bool) {
return models.ApiPermApiMod, true
case "/auth/delete":
return models.ApiPermApiMod, true
case "/user/create":
return models.ApiPermManageUsers, true
case "/user/changeRank":
return models.ApiPermManageUsers, true
case "/user/modify":
Expand Down Expand Up @@ -331,6 +335,36 @@ func createApiKey(w http.ResponseWriter, request apiRequest, user models.User) {
_, _ = w.Write(result)
}

func addUser(w http.ResponseWriter, request apiRequest) {
name := request.usermodInfo.newUserName
email := request.usermodInfo.newUserEmail
if len(name) < 2 {
sendError(w, http.StatusBadRequest, "Invalid user name provided.")
return
}
if len(email) < 4 || !strings.Contains(email, "@") {
sendError(w, http.StatusBadRequest, "Invalid email provided.")
return
}
_, ok := database.GetUserByEmail(email)
if ok {
sendError(w, http.StatusConflict, "User already exists.")
return
}
newUser := models.User{
Name: name,
Email: email,
UserLevel: models.UserLevelUser,
}
database.SaveUser(newUser, true)
user, ok := database.GetUserByEmail(email)
if !ok {
sendError(w, http.StatusInternalServerError, "Could not save user")
return
}
_, _ = w.Write([]byte(user.ToJson()))
}

func changeFriendlyName(w http.ResponseWriter, request apiRequest, user models.User) {
ownerApiKey, ok := isValidKeyForEditing(w, request)
if !ok {
Expand Down Expand Up @@ -725,6 +759,8 @@ type userModInfo struct {
basicPermissions bool
deleteUserFiles bool
newRank string
newUserName string
newUserEmail string
}
type fileModInfo struct {
id string
Expand Down Expand Up @@ -811,6 +847,8 @@ func parseRequest(r *http.Request) (apiRequest, error) {
grantPermission: r.Header.Get("permissionModifier") == "GRANT",
basicPermissions: r.Header.Get("basicPermissions") == "true",
deleteUserFiles: r.Header.Get("deleteFiles") == "true",
newUserName: r.Header.Get("username"),
newUserEmail: r.Header.Get("email"),
},
}, nil
}
Expand Down
90 changes: 90 additions & 0 deletions internal/webserver/web/static/apidocumentation/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,65 @@
}
}
},
"/user/create": {
"post": {
"tags": [
"user"
],
"summary": "Creates a new user",
"description": "This API call adds a new user. The new user does not have any specific permissions and is userlevel USER. Requires API permission MANAGE_USERS",
"operationId": "createuser",
"security": [
{
"apikey": ["MANAGE_USERS"]
}
],
"parameters": [
{
"name": "username",
"in": "header",
"description": "Full name of new user, must be at least 2 characters",
"required": true,
"style": "simple",
"explode": false,
"schema": {
"type": "string"
}
},{
"name": "email",
"in": "header",
"description": "Email address of new user. Must be at least 4 characters and include an @ sign",
"required": true,
"style": "simple",
"explode": false,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Operation successful",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NewUser"
}
}
}
},
"400": {
"description": "Invalid parameters supplied"
},
"401": {
"description": "Invalid API key provided for authentication or API key does not have the required permission"
},
"409": {
"description": "A user already exists with that email address"
}
}
}
},
"/user/modify": {
"put": {
"tags": [
Expand Down Expand Up @@ -1010,6 +1069,37 @@
"description": "NewApiKey is the struct used for the result after creating a new API key",
"x-go-package": "Gokapi/internal/models"
},
"NewUser": {
"type": "object",
"properties": {
"email": {
"type": "string",
"example": "user@gokapi"
},
"id": {
"type": "integer",
"example": 14
},
"lastOnline": {
"type": "integer",
"example": 0
},
"name": {
"type": "string",
"example": "Gokapi user"
},
"permissions": {
"type": "integer",
"example": 0
},
"userLevel": {
"type": "integer",
"example": 2
}
},
"description": "NewUser is the struct used for the result after creating a new API key",
"x-go-package": "Gokapi/internal/models"
},
"body": {
"required": [
"file"
Expand Down
8 changes: 6 additions & 2 deletions internal/webserver/web/static/css/cover.css
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ td.newItem {
}


@keyframes subtleHighlightNewApiKey {
@keyframes subtleHighlightNewJson {
0% {
background-color: green; /* Pale green for new items */
}
Expand All @@ -258,7 +258,11 @@ td.newItem {
}

.newApiKey {
animation: subtleHighlightNewApiKey 0.7s ease-out;
animation: subtleHighlightNewJson 0.7s ease-out;
}

.newUser {
animation: subtleHighlightNewJson 0.7s ease-out;
}


Expand Down
2 changes: 1 addition & 1 deletion internal/webserver/web/static/css/min/gokapi.min.3.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions internal/webserver/web/static/js/admin_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,38 @@ async function apiFilesDelete(id) {

// users


async function apiUserCreate(userName, userEmail) {
const apiUrl = './api/user/create';

const requestOptions = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'apikey': systemKey,
'username': userName,
'email': userEmail

},
};

try {
const response = await fetch(apiUrl, requestOptions);
if (!response.ok) {
if (response.status==409) {
throw new Error("duplicate");
}
throw new Error(`Request failed with status: ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error in apiUserModify:", error);
throw error;
}
}


async function apiUserModify(userId, permission, modifier) {
const apiUrl = './api/user/modify';

Expand Down
Loading

0 comments on commit fa6e000

Please sign in to comment.