Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[REST Auth] API tokens & openhab:users console command #1735

Merged
merged 12 commits into from
Oct 25, 2020
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.io.console.internal.extension;

import java.util.List;
import java.util.Optional;
import java.util.Set;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.auth.ManagedUser;
import org.openhab.core.auth.User;
import org.openhab.core.auth.UserApiToken;
import org.openhab.core.auth.UserRegistry;
import org.openhab.core.io.console.Console;
import org.openhab.core.io.console.extensions.AbstractConsoleCommandExtension;
import org.openhab.core.io.console.extensions.ConsoleCommandExtension;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

/**
* Console command extension to manage users, sessions and API tokens
*
* @author Yannick Schaus - Initial contribution
*/
@Component(service = ConsoleCommandExtension.class)
@NonNullByDefault
public class UserConsoleCommandExtension extends AbstractConsoleCommandExtension {

private static final String SUBCMD_LIST = "list";
private static final String SUBCMD_ADD = "add";
private static final String SUBCMD_REMOVE = "remove";
private static final String SUBCMD_CHANGEPASSWORD = "changePassword";
private static final String SUBCMD_LISTAPITOKENS = "listApiTokens";
private static final String SUBCMD_ADDAPITOKEN = "addApiToken";
private static final String SUBCMD_RMAPITOKEN = "rmApiToken";
private static final String SUBCMD_CLEARSESSIONS = "clearSessions";

private final UserRegistry userRegistry;

@Activate
public UserConsoleCommandExtension(final @Reference UserRegistry userRegistry) {
super("users", "Access the user registry.");
this.userRegistry = userRegistry;
}

@Override
public List<String> getUsages() {
return List.of(buildCommandUsage(SUBCMD_LIST, "lists all users"),
buildCommandUsage(SUBCMD_ADD + " <userId> <password> <role>",
"adds a new user with the specified role"),
buildCommandUsage(SUBCMD_REMOVE + " <userId>", "removes the given user"),
buildCommandUsage(SUBCMD_CHANGEPASSWORD + " <userId> <newPassword>", "changes the password of a user"),
buildCommandUsage(SUBCMD_LISTAPITOKENS, "lists the API tokens for all users"),
buildCommandUsage(SUBCMD_ADDAPITOKEN + " <userId> <tokenName> <scope>",
"adds a new API token on behalf of the specified user for the specified scope"),
buildCommandUsage(SUBCMD_RMAPITOKEN + " <userId> <tokenName>",
"removes (revokes) the specified API token"),
buildCommandUsage(SUBCMD_CLEARSESSIONS + " <userId>",
"clear the refresh tokens associated with the user (will sign the user out of all sessions)"));
}

@Override
public void execute(String[] args, Console console) {
if (args.length > 0) {
String subCommand = args[0];
switch (subCommand) {
case SUBCMD_LIST:
userRegistry.getAll().forEach(user -> console.println(user.toString()));
break;
case SUBCMD_ADD:
if (args.length == 4) {
User existingUser = userRegistry.get(args[1]);
if (existingUser == null) {
User newUser = userRegistry.register(args[1], args[2], Set.of(args[3]));
console.println(newUser.toString());
console.println("User created.");
} else {
console.println("The user already exists.");
}
} else {
console.printUsage(findUsage(SUBCMD_ADD));
}
break;
case SUBCMD_REMOVE:
if (args.length == 2) {
User user = userRegistry.get(args[1]);
if (user != null) {
userRegistry.remove(user.getName());
console.println("User removed.");
} else {
console.println("User not found.");
}
} else {
console.printUsage(findUsage(SUBCMD_REMOVE));
}
break;
case SUBCMD_CHANGEPASSWORD:
if (args.length == 3) {
User user = userRegistry.get(args[1]);
if (user != null) {
userRegistry.changePassword(user, args[2]);
console.println("Password changed.");
} else {
console.println("User not found.");
}
} else {
console.printUsage(findUsage(SUBCMD_CHANGEPASSWORD));
}
break;
case SUBCMD_LISTAPITOKENS:
userRegistry.getAll().forEach(user -> {
ManagedUser managedUser = (ManagedUser) user;
if (!managedUser.getApiTokens().isEmpty()) {
managedUser.getApiTokens()
.forEach(t -> console.println("user=" + user.toString() + ", " + t.toString()));
}
});
break;
case SUBCMD_ADDAPITOKEN:
if (args.length == 4) {
ManagedUser user = (ManagedUser) userRegistry.get(args[1]);
if (user != null) {
Optional<UserApiToken> userApiToken = user.getApiTokens().stream()
.filter(t -> args[2].equals(t.getName())).findAny();
if (userApiToken.isEmpty()) {
String tokenString = userRegistry.addUserApiToken(user, args[2], args[3]);
console.println(tokenString);
} else {
console.println("Cannot create API token: another one with the same name was found.");
}
} else {
console.println("User not found.");
}
} else {
console.printUsage(findUsage(SUBCMD_ADDAPITOKEN));
}
break;
case SUBCMD_RMAPITOKEN:
if (args.length == 3) {
ManagedUser user = (ManagedUser) userRegistry.get(args[1]);
if (user != null) {
Optional<UserApiToken> userApiToken = user.getApiTokens().stream()
.filter(t -> args[2].equals(t.getName())).findAny();
if (userApiToken.isPresent()) {
userRegistry.removeUserApiToken(user, userApiToken.get());
console.println("API token revoked.");
} else {
console.println("No matching API token found.");
}
} else {
console.println("User not found.");
}
} else {
console.printUsage(findUsage(SUBCMD_RMAPITOKEN));
}
break;
case SUBCMD_CLEARSESSIONS:
if (args.length == 2) {
User user = userRegistry.get(args[1]);
if (user != null) {
userRegistry.clearSessions(user);
console.println("User sessions cleared.");
} else {
console.println("User not found.");
}
} else {
console.printUsage(findUsage(SUBCMD_CLEARSESSIONS));
}
break;
default:
console.println("Unknown command '" + subCommand + "'");
printUsage(console);
break;
}
} else {
printUsage(console);
}
}

private String findUsage(String cmd) {
return getUsages().stream().filter(u -> u.contains(cmd)).findAny().get();
}
}
46 changes: 41 additions & 5 deletions bundles/org.openhab.core.io.http.auth/pages/authorize.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@
margin-bottom: 2rem;
}

form.hide {
display: none;
}

form.show {
display: initial;
}

input {
border: 0;
padding: 10px;
Expand All @@ -54,41 +62,69 @@
margin-bottom: 1rem;
}

input.submit {
.submit {
cursor: pointer;
margin-top: 2rem;
background-color: rgb(33, 150, 243);
text-decoration: none;
color: white;
border-radius: 4px;
font-size: 14px;
font-weight: 600;
min-width: 80px;
padding-top: 10px;
padding-bottom: 10px;
padding-left: 1rem;
padding-right: 1rem;
}

input.submit:hover {
background-color: rgb(72, 168, 245);
}

.result {
display: none;
}

.resultPassword {
display: initial;
}

.resultToken {
display: initial;
}
</style>
</head>
<body>
<img class="logo"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoEAAACNCAMAAAD/0FDYAAAAk1BMVEUAAABHR0eYmJjmShlHR0eYmJjmShlHR0eYmJjmShlHR0eYmJjmShlHR0eYmJjmShlHR0eYmJjmShlHR0eYmJjmShlHR0eYmJjmShlHR0eYmJjmShlHR0eYmJjmShlHR0eYmJjmShlHR0eYmJjmShlHR0eYmJjmShlHR0eYmJjmShlHR0eYmJjmShlHR0eYmJjmShkF3IrcAAAALnRSTlMAEBAQICAgMDAwQEBAUFBQYGBgcHBwgICAj4+Pn5+fr6+vv7+/z8/P39/f7+/vsRLa0QAAGIlJREFUeNrtndeCsjoQgFGxIXYFdbEsuPay7/90hy4JKZNQdP/DXJzihtSPSWYyJIqSSWqaNjJi6WuaUkkl5YjaN7b2Iy1X2zK0WtU/lRRK33R7ejDlxxpVFFZSjPSXHPpiCo121VuV5Cxt6/oQkJOhVn1WSW5Sm54ewmKPqo6rJJ/Fn/WQk6tRLQkrySyaLH8+g8tqMq4km/7bPrJJpQcrybL+sx7Z5WpUHVmJnEyvj1zkVG2YVCLjf/l55CbbaiquRFQMps95a4wSW8E1TZsaNktjXvtVj1YiZIHQFaBt0CbVWn9Jf8yq1GAlcBlR9NlpydNltRFt8+RUbdVVApUleSa1gAz1yS6ca7VJUgnMB2MTl34iMS+qQdzGW1adW4mkDWwLO1RGp8omrkQKwGse/NEY/KkQzDA0WiyA9ZAqlDoWL70qlpwuaj4AyjuUCS7tCkF5SayObH5qQyh1vIb3pyp4en6I1Nboi4z5KN9NNcK23rUyiT+ZwKAMNT8CA70zrUkDaGcMbtFOFYJ/iEAtdN7mTCA4QqWdem6a3bJeVhPx3yEwKkLNm0AYS6k14E8u6qp/rRD8IwRqwn6zXKMD1GtBG2mpLb6fiqbPJDBetl9rBRDIUz01nJNpbl2XMkisCqdPJFB9PWIUQSAHQbvIPTR8MVhFrX4igQlFAVWCop+vwSpchMWKW9lV0OrnEagmB2hUCIEM1dMv2mWCIXitPmH6OAKRtdKpGAIfNLBq18J9diO4Pq7kHQRiDIxECTRSsrV/wPOwXYLTeFQtBT+aQGwddhIlkOLgWV4h668pTFNmlHJKqQiUI7B2lVmqcwlU8PAA4qazKqN/JcSqvIKfS6AhYrcKEYhtddS4c7BRSh9W8/CHEZiOpdPyIhDd7h3x7OBtgX1YO1X28IcSOCLsouVGIKJgLQ4Wp0I3bdulwV4RKEYgKaRdzY3AGnN2N8q0D6a5+KW7ui+NTFVpBJl0M2XSyp5JC9qUIgkcEb+wzY1AZP3FtoFE12athvyLLOUU1Bf7y28sx81cZvBbk83+lcl9vxhKwNydf1+QTHTxtiSqcdl/TVpvI5B8QKSaG4FLRkIri33avR8Fxw61uwXN7uHmNy2Xr5YYfvMjIZfviVjDN3dSJkO4Bp58E9vSfQuBGnkDY5kbgQY9oZplDu66wyCK4FRi7ycYs/nllyJ7XQAdWib3Bbglwz0tkwsskxYR4CCHyRsItClRpbUSCLQyzMFdvxtFEfyRU4ITKn8+g7DJuLVhZXJfwCbPPTOTOf9dWrAy+L3oZROoiQcSCBJoURMiq8BrTQJAYQQ1mZVgiznqnkDomd85mVz4IDe+eDU5cjLpXng5fJVMoI0cQp8kIi8Cf6gJDflVWTceTkEELXFzeMJDBzDwSmPPz4QLcvcIyISpBieADIg9WhiBaFgWopRG+RCo0m2Nk+SiLAmgKIKqcLj05hcidx1cYYZ8M9uiwzLZZAPQ7dFWiQRaKASGABNAAi2qF7gvrQLR8RRD0OJtE0oB6MokO4DstkygNdk3MuZAqEVRBOKRqSKRqhK7cujXH1tZFYiPpxCCqtjnKPvf3+wI6ndwJvS2TH6zZqLDMyiNQAs3BSy4fw5EIPoJksqiXxpAQQQtEfA3v7/ZEezefzPTI5YJcSJuCOSwKIlANWX8qvCFOoRAFECb6pu7ZgJQDMG2gA+SAODxazHU9cnim2BUks2RVrrC9+/FXNf1xRdBxX4rMHzcPIb+rtxicwEhmGrMcTHRaTm0yiHQSHtDLLC3AkAgdhjbiGojG9kAFEPQBjveUxPffpIop/uFV+VOqkUDN2Dvyb2HxmQPsoiPzB2QdFXS+riF+X7mDZazfF8KgYjpaxA0hJaJwLbFCr1WBQMhOJORAIIj6DSMl5Xe/cB9fHtCLl+83Y+Uu5FgVmNu5H0r7WhGq3JvMVXgnUDoN0ufF0OgQXII21BvBYtAVRulr2PtUydhOzOAIggiLx5rGj7y9xsa3zxnnM5jx9toQ1t1STWli9aEuAOMVQV/GRr8zpow5nE76TbmCpBXZCQs4q6BCiPQTgrtSHsM562MHcJajsMRtGDW8Bzkc56w5+HGBeJzxmbqL6ZFfmyBFg0T+h9pXZVMcwdt3mb7TnxKZg2qBAVrgh+aIOiWA9iDYAT7oA5qAJ2OqALbMKdPqsMGU2AtBlqMZiL9c0H/9s2aoUm11Ysn8ERGrQ9UgoI3vNXoG7TbXAAUQPAKseMX0Ky7DHhQjFlO628GxxfmFE2rClraHbL516CmKoLAEY20E8xIFdOAbcYadCoJ4GQjieAWYG0h7DCHnbV+WoB3bNGJuEXNvwu23i80NhmNWdAWkkUQeKLNtiNYxIqQBlRZLhFVEsC0iwuI4AjwjiVXgXdO3AFi7jYomofi6CO7Db9oq0Be8MKeMo9OYPVo0RaCBRA4ortdYEow21W/V9EduS7R4yWHoApYAlygygu3muc0lcSr2pBiB7R4Hkea129DVm7MgOxEs4sm0KanGoF2K0Sv+h3RGLDkAZRF8MTlv8vcJWW5XI6UxR0/iH5PTv0l8i4kO+ROrkkX+LheLIEaw/MMC9LKdM2qJrgM7FJ9/lIIbrmmyILtIWa5exNrOI6zGqa/jgIqEH11dCLdzMcXpRFosxKBgrQkbptuE0vQsgAohyC/+L2ICkTHfUJUjZDviL5JZkSLG79MXREsSLPrBboUWBRKoEbfrIAqQYnavM7FsqAR1lwApRDUuO0D+lBI474hKZQLJJMhSZMmfzzu+XIh6l2oMtbLIpATm2cAgrQeWRC0RQyRLmfbXRxBlWdr6TDnBdF0PpIUKUR7IabzkLIlLCL3Qgi82lw5cQnkxeZBgrSY5weitUjvjJxADiMYgDII8gyhidgkjE7DJMMS9jHvN2H89/IE/ooT2KB4bfKOTOBGaQKCtPjzqNq3TpTvkAW+TOYDKIHgD6d5C1HtpZAmUFFFmtSkG3J8hJjohBdkAW3HvkAC+eHJKj+ME7aS69vEY7ngsYEQAMUR5HXoRnAZiKgqPW1DXGCZ6ITx/82DQP3DCLT4Dj+L67CD2hLY7W42zBIQA1AYQZ4ltBfyxWATqM7gCeyPyYXA4YcSWAN8md7mxieArdnaNqUENagzBgqgKIJG7gQu8iBQyZnAxYcSaEA2frlBWgL+FAtXglAC4QAKIvj5BF7+YQJJwfkcj2EtG4EK/sUckEARAMUQrHTgOwlEDsvoU29J58UniBCoYi5uGIFiAAohWBH4TgKvEp7kWjYCkRHfAgkUBVAEwfwJ3OdBIMcWVqTlowgcyWxmGBkJVFEHJIRAcQAFEOQRuBA7Fwt123UJGyuwTIaE8b9Ao1P/DIEnGQJPGQnETtACECgDIBxBAQI3oCFuEGjTf0XhWbD3RIb/BIFSKpDktRMj0EA83HwC5QAEI8gjUBd1Jg8JD+hCgX2YIiWFn339EwSe5Ag85UigxidQFkAogjwCG6Lqa0PYT9XhIfoph/Sr1LnoFrUyb3wygX3ZKJtRNgI1KoH9XAEEIsgjMLn6AmmeO0Hd6ayDWIjokMJauoKZuFN5+jjoDyIwvzivLDqwxjZysgAIQ9DmRZ9thAKTkQ9CukQCIQbNhagz72KZ+IoUZ/BzCNQe0qLlSCA7MiEbgCAEuR06FIPnQlo36txTjegYT6Rfhj3pkJvPIdCWJ9DO0RZWWMo1K4AQBK/cDr2LjPucGEmv/wopQeSAj4bsy/CqSvKI/48hsP3IIO2c/IEP9iyYHUA+gjX+vuSXgBmBfN3eohDItWgWtO/eL5ADN0i91/o8ApGwLNsWCrfG4xOECFxiSo/+sVoeAHIRBHyq1xKow578TZwuFDDWpR7gtoB/coecvPD9eTpQFb5BhnGSltC+MB4mbdB0az4A8hCcAvZkNuBDE74o7OiQs3Vj4u9UzhAVu4A3u/V5BFriN8jQg7SkY2P6mF98VACAHAQtwJkhiBJkITihHdun/8IRRI+N0enzM7NHNrTSPoRAcRWI+Q9rkgQi8YFXfEE6LQJANoI/kEMhENVGvy/ki7rYS59dv6FNxOhVR9+sdSbjCLgN9RTVDyFwKXNwLjVISzZG2sKetooBkIVgTeIAQcrIYUfwLhQmgbRzMNFrm9LmxhCiSrEbl+af540hn5nKE+pJWpLfiYTo24UDyECwD5sLcEuCoAaxg6SPCodAF4AGj2JSs/dcjvEr544f6JE25M4OP1HsRth9IiObfJjvsngA0wjOCauCPtTE8I/SR8lZYJcgYKtF8g0yd+xK4uE3YLWIX+awwdRk6sZWTI9+BIFyKpB+9DyTwLamaSPD+qGeJNgvAcAUgi3CS1WDulmCgf2e634u3eEi/SHvRAEQ6Omnr6GvTxv6fAO8nieV13EeqeSGTqjK8AN35SRVIPUQGTm39hRZihUMIIbgkeCX/4G717gyV4AEMk/aaPC3XeINQO+cmDszPP+TCDxJXZ+gUE/SkgJwi5ijhQOIIjghWGRLJTcEN0oOBNLdPiIXjLGq8jYCR+CraphKsJ+JwMSB+kYpACYHLtYuJ4GegCO4UXIgkOV33PzmUpW3ESitAlElaGchMGlLt8sBMDFwG8I+D/9COyiCc+baDXip4LElYljRZKJ8IoHyKpB2kpaEBkT234xyAHwNXItgCQMssgZk4InXHCW/fANdjfnNCcIBXfBK9J1/AIE/4NU3Z0/Dlibwh2Z2FgtghOCG4I5m+mJe/hIuPcS7utBvL/H7vwjo8L8l6R4lKX4/gRr3tCywElQlCaSu+YsGMESwRVhTAG+Vxb29uEk65Hq0vdHUL+zFG+iAtwX7bbjo3Kq8iUAbcjI0TAlaUgT+aO8D0Edwk8kt2qJPxZcJYE8lGM0JncFNC1iTBoNBUFXeQ2A2FUhWgkJf2tHLLANAF8E7SQXyLrhGGJyTZsD79xC0qxeNpr4h8XNZtATa0pgQJ/T7RgdV5T0EZlSBaESpJUjg1crtcCJ50UkqUHBB3JpskhTe9wv2J+TkUzv0BeJEPm7mLeHWNIZfSCZuVXRoVd5CoPoQvL4jJVrapwJTflsjp/MBcxIj42ygtPRQANTTQ5ujTFqZ3ip4VRpxWl6Rr4RdfJs1FMDEoeKp1eSZWDWp1qZz0PjCLap0ABEVeCq4MImzsyopWcoGEI2VNSoC//eyLxlAjXckXUXg/0z0cgFEv1cxlIrASvalAmiUqgIrAv+YEiweQO2R2SfwbxBYr0s/2nGc1Ztq7RbtFJDtd3kA1k6PEg3hDyZw9nxKU9R7Pp03Vdst+llAttGH2sUDqGwfGeKD/iUCbxmG8t8jMPwauwQADUqw9v+PwMPzeRZ9pjl4A4GDZhkE+ifXxgD220W1ZvQo1wz5YAI7u11H8IlDxF2JBK6ez14pBOoJAEf0IMKM0r6WbIb8U7bwi7sSCXTKIlD5miT1VDEIYgBulfcT2OxhFmm9F006nV4KgQ7+cBM2eeKFdNLaD8uKlHWKwDqWL7E+dazWcbOwp3vpnmARiBfVwR9P5I81F68ReaIsAkEMwFLmYCaBnbXbnc+bGfSU5+EYuKuz59ldbdXXnqWwDv4ydpyOMvb+9FzHHd/b+Q+v/d40HWcW/LxznOChQegxCdKdE4WMz94gupmOA++GqfQcvx7xEJnnoF4zr+BoOnTcCtwcP1efwLiuIX5+jW9rFASvrOfzMAhbUfdbvHLTDM6Jp5vrc1DJqCjHrZeLnls5N9ODk6iGT6Bf37P5KuWQbGPH6wy/YO+x5o7QtkQ/Uldq+SOIAViGHcwmcPwM5eD3nPvv6Jdx55b8i+kOxi780y0ci3X08HMWZHUIet97OkowIxXi+L/4mXrD0nR/MaMk8Xov/P9DYsZzooycgMCoAuHYDm5YBZFaroJWjKNmxX8I6vpM5B3qPS9lrxf/oZckMGrTOuQ3qm9U9qt2t07Uk6H27hCrSTYV8kYQB9BQ3kzgwBu+XtN7f514GBxz5fbR7eC+0KYTja87du6Lu57NvH69NaOhXQ96/svvglZ3/9UMF+7hwNz8X8be+9/reBphHRTi6wYnJtD95eYV66sG/4f6wc97EAzi630x156aMc1xgIGbxvTVXi8a2XWv4+nFAzJl3ma93urcCVpxe55XfrMO3tN+S/33Ynde9dxkEZGOX6eD02n61Vq7hTaTBD6fO//hIHX95rexZ94irqLaeXrx5tX5nEy76zW99/lW59qq+SKIA7hV3kyg1xlBr5yDUYxUQifWWF4/hQQ+b/5Ae6phF2q2QH3sAvac8P/96TNUhoegkE5UWicsxIkUq6kkivXAu4UQBwPZeaKrfmQdGKTxwQt9O2MF+Y/wPXlNlWaiWa+W+ouHZpziEKnbW49uifh/87rtEPZA8FZ6lYnf5V6k/9dh2w5hhdZRt5kAZ0mOTpkR9JO90ggcx7NI+F+x9lpHCs2dIZ+h9oiGdRb87Rw/7KG18n9fB2Pq9u0g4GiW3PgI/8tTC3UFIzAYi7C0eoRFUC6VwF6U5BzocyfW7LvkzD1DCIybtYpauku+lGF5ZmLaJRE4jueQMLceWq24SYdI1fXitKEPtEn2hqp4ZP8oLwyWeM5t5d0E7uKO6wTDF/fkOB7OQ/CTGWqncHId+73efDnMznGPuv/TCVg8RKqxg+CD4BURGGYV6OLBq7Amg8ADYpquXpovUVdfnY4TBDpxG/CWesbsLCLPTEzlDG9MkHqWSHwO2hQ3yYwJD96uxE6km7QO0FS8A12AUrPfByCVQG/KCCXo1JjA10g78Yg4iRExvRRnbEgOfhcf3NE4hEQegjzDMg4Y5kkCkcFOFsYg0EGSOIHF+mpMiLC3WDvPYqW7wqiK8+msbwmTw0xMkVwC14n97XXcSLSN0dvlrWTDat6wfCMx2CcsyImWuku5r7yfwCciYAJXXqfOEj+8tNDM427m60FPu8zwQsAEmjIEoo2JFNv5GftCXhmnCFwFFnKEhRCBTiKxSSXQidImhEggGkDvq6usGxe1JeCK0AKltYhlghFoxjIW04HjNIHeCtAjs+n918qb45vMQlgErmUIXL8KQpa7TuSyoRPoLW59v6ZTPIGJalKi06z0NU6Z1GD/9F4AqXKOl2hIh/IIPHsrruTW1Cyckc/u+uvgT71n95+3YHGUWC9ihVAJnL1m+KYIgQPqjozn3amzCDxHi1MZAlcJa8aJzS0ygWuKBcxD8LGUNlxV+/GhAKb9AUwCI+9Vx2eq/jKO/fc6XAP1gpF0x2QQjqlDLYRKYAdJAiRwlVCc6X25Gza9YwS+QJdZB46fsU3hZTRgEThDvJVUmRI+d5dzH9cINF+1zwDQ9xV3wARGAxwaFOvYqTKOHhv4ft5m6AoMdR+9ECqB3r+CvDu3FIFnCoGdZ9INiMuBTWAvYmhMJnDMJLB+izsn8p9SCUx4moR8d34w81RYD6rGlQBgW/kU8ZxVvplYHwz4BD53TcXfLPV/8YzMQydcQu1i4zp87vzaYfMKGQeF9IAEejT5OxmYVeGrmJnvPk4R6HuZg4XVIAHMzP9pzJuFwxdsfCMQ6GZ8aCpNOoHBKtLNv74LVSCdwOBd9jPrmWLGq68HVZHxbVvEo5PUjwHQd+qHBuCBPwsHO0yIOzZ4NtzwDSCIt4SjVz3YB/UTOkACX1vJKywSyok2gtME1oPYAK+cG7LWPTjn174whcCV/+jZbyVO4Bgzr9MEBnvMvhso9MvQCQzS3vw6MeOK1B/y+dNQL0ptRMmgpnyQ1OPogjPfEgk39WMHbxQ+cEPGqh7jGfVv0yEXwiAwULVe4ApGYIAzkUClvorDA5pJAuPABAaB9V3YtlWgxJIEKmsugcosdCZGoTYMAhWTG5pAX8EF5x/1AfhtOUf4f4w0Z6azM2dBX/SiCLl6Lwpg6wQ/+bZwfbZyzHHCg9Ax18gPr8e8rHiFKM1eEGL3Stt5hejV/T92EIXmZ7XbmR1KUc2xV46J2g3mylnNmkiBiYLifHqmm6weJXml9N8st92zZCt7r6LD+tYH5m5txtZ4nIRQpFIfmzsHqyZ5MXilHcK1nTLWcppBvb771Fb+qJhv+jBojG7b/t+EMhOHtyIbI/xEJFWbLlmXx1s1pSJQeKE6U/7PYlx5R7Latm0Zxha7hJvIbP8Pd0TJBK6DaLzegRpE9/9Rg/YjJ1nWlIpAqPiWqW8u0jc6/jfSP+XBn93+271QLoHNc2w9d5RK+FMx/wTX/l/vg14QVVCaDEwvfsmsFGDoXMnG4GlUdWElmRmUnot/Kv4qyUVGUjaJpVU9V0ludvFSUBH+TGtVr1WSq7ThEP5M1aq/KikCwumWf3fOdlThV0mhFFo2dbduWdFXSTnLQm1qLG1PTj/+v4z0TnEllTDlP+Wgb6SN+7dxAAAAAElFTkSuQmCC" />
<form method="POST" action="/auth">
<div class="message">{message}</div>
<form class="{formClass}" method="POST" action="{formAction}">
{form_fields}
<div class="message">{message}</div>
<div>
<input class="field" autocomplete="off" type="text" placeholder="User name" name="username" required autofocus />
</div>
<div>
<input class="field" type="password" placeholder="Password" name="password" required />
</div>
<div>
<input class="field" type="{repeatPasswordFieldType}" placeholder="Confirm Password" name="password_repeat" />
<input class="field" type="{newPasswordFieldType}" placeholder="New Password" name="new_password" />
</div>
<div>
<input class="field" type="{repeatPasswordFieldType}" placeholder="Confirm New Password" name="password_repeat" />
</div>
<div>
<input class="field" type="{tokenNameFieldType}" placeholder="Token Name" name="token_name" />
</div>
<div>
<input class="field" type="{tokenScopeFieldType}" placeholder="Token Scope (optional)" name="token_scope" />
</div>
<div>
<input class="submit" type="Submit" value="{buttonLabel}" />
</div>
</form>
<div class="result{resultClass}">
<a class="submit" type="button" href="/">Return Home</a>
</div>
</body>
</html>
Loading