Skip to content

Commit

Permalink
Merge pull request #570 from jmpsec/token-exp-modal
Browse files Browse the repository at this point in the history
Specify expiration for tokens in osctrl-admin and osctrl-api
  • Loading branch information
javuto authored Nov 19, 2024
2 parents ac7197a + 192b624 commit ca936b9
Show file tree
Hide file tree
Showing 11 changed files with 102 additions and 56 deletions.
4 changes: 2 additions & 2 deletions admin/handlers/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -1278,7 +1278,7 @@ func (h *HandlersAdmin) UsersPOSTHandler(w http.ResponseWriter, r *http.Request)
return
}
if u.Token {
token, exp, err := h.Users.CreateToken(newUser.Username, h.AdminConfig.Host)
token, exp, err := h.Users.CreateToken(newUser.Username, h.AdminConfig.Host, h.Users.JWTConfig.HoursToExpire)
if err != nil {
adminErrorResponse(w, "error creating token", http.StatusInternalServerError, err)
h.Inc(metricAdminErr)
Expand Down Expand Up @@ -1358,7 +1358,7 @@ func (h *HandlersAdmin) UsersPOSTHandler(w http.ResponseWriter, r *http.Request)
return
}
*/
token, exp, err := h.Users.CreateToken(u.Username, h.AdminConfig.Host)
token, exp, err := h.Users.CreateToken(u.Username, h.AdminConfig.Host, h.Users.JWTConfig.HoursToExpire)
if err != nil {
adminErrorResponse(w, "error creating token", http.StatusInternalServerError, err)
h.Inc(metricAdminErr)
Expand Down
2 changes: 1 addition & 1 deletion admin/handlers/tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func (h *HandlersAdmin) TokensPOSTHandler(w http.ResponseWriter, r *http.Request
if h.Settings.DebugService(settings.ServiceAdmin) {
log.Debug().Msg("DebugService: Creating token")
}
token, exp, err := h.Users.CreateToken(user.Username, h.AdminConfig.Host)
token, exp, err := h.Users.CreateToken(user.Username, h.AdminConfig.Host, t.ExpHours)
if err != nil {
adminErrorResponse(w, "error creating token", http.StatusInternalServerError, err)
h.Inc(metricAdminErr)
Expand Down
1 change: 1 addition & 0 deletions admin/handlers/types-requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ type AdminResponse struct {
type TokenRequest struct {
CSRFToken string `json:"csrftoken"`
Username string `json:"username"`
ExpHours int `json:"exp_hours"`
}

// TokenResponse to be returned to API token requests
Expand Down
109 changes: 62 additions & 47 deletions admin/static/js/users.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
function addUser() {
$("#user_username").val('');
$("#user_email").val('');
$("#user_fullname").val('');
$("#user_password").val('');
$("#user_username").val("");
$("#user_email").val("");
$("#user_fullname").val("");
$("#user_password").val("");
$("#addUserModal").modal();
}

Expand All @@ -15,12 +15,12 @@ function confirmAddUser() {
var _email = $("#user_email").val();
var _fullname = $("#user_fullname").val();
var _password = $("#user_password").val();
var _admin = $("#user_admin").is(':checked');
var _token = $("#user_token").is(':checked');
var _admin = $("#user_admin").is(":checked");
var _token = $("#user_token").is(":checked");

var data = {
csrftoken: _csrftoken,
action: 'add',
action: "add",
username: _username,
email: _email,
fullname: _fullname,
Expand All @@ -32,34 +32,34 @@ function confirmAddUser() {
}

function confirmDeleteUser(_user) {
var modal_message = 'Are you sure you want to delete the user ' + _user + '?';
var modal_message = "Are you sure you want to delete the user " + _user + "?";
$("#confirmModalMessage").text(modal_message);
$('#confirm_action').click(function () {
$('#confirmModal').modal('hide');
$("#confirm_action").click(function () {
$("#confirmModal").modal("hide");
deleteUser(_user);
});
$("#confirmModal").modal();
}

function changeAdminUser(_user) {
var _csrftoken = $("#csrftoken").val();
var _value = $("#" + _user).is(':checked');
var _value = $("#" + _user).is(":checked");

if (_value) {
$('#permissions-button-' + _user).hide();
$("#permissions-button-" + _user).hide();
} else {
$('#permissions-button-' + _user).show();
$("#permissions-button-" + _user).show();
}

var _url = window.location.pathname;

var data = {
csrftoken: _csrftoken,
action: 'admin',
action: "admin",
username: _user,
admin: _value,
};
sendPostRequest(data, _url, '', false);
sendPostRequest(data, _url, "", false);
}

function deleteUser(_user) {
Expand All @@ -69,7 +69,7 @@ function deleteUser(_user) {

var data = {
csrftoken: _csrftoken,
action: 'remove',
action: "remove",
username: _user,
};
sendPostRequest(data, _url, _url, false);
Expand All @@ -84,40 +84,49 @@ function showAPIToken(_token, _exp, _username) {

function refreshUserToken() {
$("#refreshTokenButton").prop("disabled", true);
$("#refreshTokenButton").html('<i class="fa fa-cog fa-spin fa-2x fa-fw"></i>');
$("#refreshTokenButton").html(
'<i class="fa fa-cog fa-spin fa-2x fa-fw"></i>'
);
var _csrftoken = $("#csrftoken").val();
var _username = $("#user_token_username").val();

var _exp_hours = parseInt($("#expiration_hours").val());
var data = {
csrftoken: _csrftoken,
username: _username,
exp_hours: _exp_hours,
};
sendPostRequest(data, '/tokens/' + _username + '/refresh', '', false, function (data) {
console.log(data);
$("#user_api_token").val(data.token);
$("#user_token_expiration").val(data.expiration);
$("#refreshTokenButton").prop("disabled", false);
$("#refreshTokenButton").text('Refresh');
});
sendPostRequest(
data,
"/tokens/" + _username + "/refresh",
"",
false,
function (data) {
console.log(data);
$("#user_api_token").val(data.token);
$("#user_token_expiration").val(data.expiration);
$("#refreshTokenButton").prop("disabled", false);
$("#refreshTokenButton").text("Refresh");
}
);
}

function showPermissions(_username) {
$("#username_permissions").val(_username);
sendGetRequest('/users/permissions/' + _username, false, function (data) {
sendGetRequest("/users/permissions/" + _username, false, function (data) {
for (var key in data) {
$('.' + key + '-env').each(function() {
var element_id = $(this).attr('id');
if (element_id.search('permission-read') > 0) {
$(this).attr('checked', data[key].user);
$("." + key + "-env").each(function () {
var element_id = $(this).attr("id");
if (element_id.search("permission-read") > 0) {
$(this).attr("checked", data[key].user);
}
if (element_id.search('permission-query') > 0) {
$(this).attr('checked', data[key].query);
if (element_id.search("permission-query") > 0) {
$(this).attr("checked", data[key].query);
}
if (element_id.search('permission-carve') > 0) {
$(this).attr('checked', data[key].carve);
if (element_id.search("permission-carve") > 0) {
$(this).attr("checked", data[key].carve);
}
if (element_id.search('permission-admin') > 0) {
$(this).attr('checked', data[key].admin);
if (element_id.search("permission-admin") > 0) {
$(this).attr("checked", data[key].admin);
}
});
}
Expand All @@ -129,10 +138,10 @@ function savePermissions(_env_perm) {
var _csrftoken = $("#csrftoken").val();
var _username = $("#username_permissions").val();

var _read = $("#" + _env_perm + "-read").is(':checked');
var _query = $("#" + _env_perm + "-query").is(':checked');
var _carve = $("#" + _env_perm + "-carve").is(':checked');
var _admin = $("#" + _env_perm + "-admin").is(':checked');
var _read = $("#" + _env_perm + "-read").is(":checked");
var _query = $("#" + _env_perm + "-query").is(":checked");
var _carve = $("#" + _env_perm + "-carve").is(":checked");
var _admin = $("#" + _env_perm + "-admin").is(":checked");

var _env = $("#" + _env_perm + "-env").val();
var data = {
Expand All @@ -143,16 +152,22 @@ function savePermissions(_env_perm) {
carve: _carve,
admin: _admin,
};
sendPostRequest(data, '/users/permissions/' + _username, '', false, function (data) {
console.log(data);
});
sendPostRequest(
data,
"/users/permissions/" + _username,
"",
false,
function (data) {
console.log(data);
}
);
}

function changePassword(_username) {
$("#new_password").val('');
$("#confirm_password").val('');
$("#new_password").val("");
$("#confirm_password").val("");
$("#change_password_username").val(_username);
$("#change_password_header").text('Change Password for ' + _username);
$("#change_password_header").text("Change Password for " + _username);
$("#changePasswordModal").modal();
}

Expand All @@ -166,7 +181,7 @@ function confirmChangePassword() {

var data = {
csrftoken: _csrftoken,
action: 'edit',
action: "edit",
username: _username,
new_password: _newpassword,
};
Expand Down
17 changes: 17 additions & 0 deletions admin/templates/users.html
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,23 @@ <h4 class="modal-title">API Token</h4>
</div>
<input type="hidden" id="user_token_username" value="">
</div>
<div class="form-group row">
<label class="col-md-2 col-form-label" for="user_token_hours">Hours to expire: </label>
<div class="col-md-10">
<select id="expiration_hours" class="form-control">
<option value="2">2 hours</option>
<option value="6">6 hours</option>
<option value="12">12 hours</option>
<option value="24">24 hours</option>
<option value="48">48 hours</option>
<option value="72">72 hours</option>
<option value="168">1 week</option>
<option value="730">1 month</option>
<option value="2190">3 months</option>
<option value="4380">6 months</option>
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button id="refreshTokenButton" type="button" class="btn btn-primary" onclick="refreshUserToken();">Refresh</button>
Expand Down
2 changes: 1 addition & 1 deletion api/handlers/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (h *HandlersApi) LoginHandler(w http.ResponseWriter, r *http.Request) {
}
// Do we have a token already?
if user.APIToken == "" {
token, exp, err := h.Users.CreateToken(l.Username, h.ServiceName)
token, exp, err := h.Users.CreateToken(l.Username, h.ServiceName, l.ExpHours)
if err != nil {
apiErrorResponse(w, "error creating token", http.StatusInternalServerError, err)
h.Inc(metricAPILoginErr)
Expand Down
3 changes: 2 additions & 1 deletion cli/api-login.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import (
)

// PostLogin to login into API to retrieve a token
func (api *OsctrlAPI) PostLogin(env, username, password string) (types.ApiLoginResponse, error) {
func (api *OsctrlAPI) PostLogin(env, username, password string, expHours int) (types.ApiLoginResponse, error) {
var res types.ApiLoginResponse
l := types.ApiLoginRequest{
Username: username,
Password: password,
ExpHours: expHours,
}
jsonMessage, err := json.Marshal(l)
if err != nil {
Expand Down
9 changes: 8 additions & 1 deletion cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -1623,6 +1623,12 @@ func init() {
Aliases: []string{"e"},
Usage: "Environment to be used in login",
},
&cli.IntFlag{
Name: "expiration",
Aliases: []string{"E"},
Value: 6,
Usage: "Expiration in hours (0 for server default)",
},
&cli.BoolFlag{
Name: "write-api-file",
Aliases: []string{"w"},
Expand Down Expand Up @@ -1703,13 +1709,14 @@ func loginAPI(c *cli.Context) error {
fmt.Println("❌ environment is required")
os.Exit(1)
}
expHours := c.Int("expiration")
fmt.Printf("\n -> Please introduce your password: ")
passwordByte, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return fmt.Errorf("error reading password %s", err)
}
fmt.Println()
apiResponse, err := osctrlAPI.PostLogin(env, username, string(passwordByte))
apiResponse, err := osctrlAPI.PostLogin(env, username, string(passwordByte), expHours)
if err != nil {
return fmt.Errorf("error in login %s", err)
}
Expand Down
1 change: 1 addition & 0 deletions types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ type ApiNodeGenericRequest struct {
type ApiLoginRequest struct {
Username string `json:"username"`
Password string `json:"password"`
ExpHours int `json:"exp_hours"`
}

// ApiErrorResponse to be returned to API requests with the error message
Expand Down
8 changes: 6 additions & 2 deletions users/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,12 @@ func (m *UserManager) CheckLoginCredentials(username, password string) (bool, Ad
}

// CreateToken to create a new JWT token for a given user
func (m *UserManager) CreateToken(username, issuer string) (string, time.Time, error) {
expirationTime := time.Now().Add(time.Hour * time.Duration(m.JWTConfig.HoursToExpire))
func (m *UserManager) CreateToken(username, issuer string, expHours int) (string, time.Time, error) {
tDuration := time.Duration(expHours)
if expHours == 0 {
tDuration = time.Duration(m.JWTConfig.HoursToExpire)
}
expirationTime := time.Now().Add(time.Hour * tDuration)
// Create the JWT claims, which includes the username, level and expiry time
claims := &TokenClaims{
Username: username,
Expand Down
2 changes: 1 addition & 1 deletion users/users_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func TestUserManager(t *testing.T) {
assert.Equal(t, 123, int(user.EnvironmentID))
})
t.Run("CreateCheckToken", func(t *testing.T) {
token, tt, err := manager.CreateToken("testUsername", "issuer")
token, tt, err := manager.CreateToken("testUsername", "issuer", 0)
assert.NoError(t, err)
assert.NotEmpty(t, token)
now := time.Now()
Expand Down

0 comments on commit ca936b9

Please sign in to comment.