Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
grishka committed Jun 17, 2022
2 parents d90542c + 9adf5d0 commit 405f3ff
Show file tree
Hide file tree
Showing 38 changed files with 972 additions and 76 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<groupId>me.grishka.smithereen</groupId>
<artifactId>server</artifactId>
<version>0.4.0</version>
<version>0.4.1</version>
<build>
<plugins>

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/smithereen/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ public class Config{
public static String serverPolicy;
public static String serverAdminEmail;
public static SignupMode signupMode=SignupMode.CLOSED;
public static boolean signupConfirmEmail;

public static String mailFrom;
public static String smtpServerAddress;
Expand Down Expand Up @@ -136,6 +137,7 @@ public static void loadFromDatabase() throws SQLException{
signupMode=SignupMode.valueOf(_signupMode);
}catch(IllegalArgumentException ignore){}
}
signupConfirmEmail="1".equals(dbValues.get("SignupConfirmEmail"));

smtpServerAddress=dbValues.getOrDefault("Mail_SMTP_ServerAddress", "127.0.0.1");
smtpPort=Utils.parseIntOrDefault(dbValues.get("Mail_SMTP_ServerPort"), 25);
Expand Down
62 changes: 62 additions & 0 deletions src/main/java/smithereen/Mailer.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,64 @@ public void sendTest(Request req, String to, Account self){
send(to, l.get("email_test_subject"), l.get("email_test"), "test", Map.of("gender", self.user.gender), l.getLocale());
}

public void sendAccountActivation(Request req, Account self){
Account.ActivationInfo info=self.activationInfo;
if(info==null)
throw new IllegalArgumentException("This account is already activated");
if(info.emailState!=Account.ActivationInfo.EmailConfirmationState.NOT_CONFIRMED)
throw new IllegalArgumentException("Unexpected email state "+info.emailState);
if(info.emailConfirmationKey==null)
throw new IllegalArgumentException("No email confirmation key");

Lang l=Utils.lang(req);
String link=UriBuilder.local().path("account", "activate").queryParam("key", info.emailConfirmationKey).build().toString();
String plaintext=l.get("email_confirmation_body_plain", Map.of("name", self.user.firstName, "serverName", Config.domain))+"\n\n"+link;
send(self.email, l.get("email_confirmation_subject", Map.of("domain", Config.domain)), plaintext, "activate_account", Map.of(
"name", self.user.firstName,
"gender", self.user.gender,
"confirmationLink", link
), l.getLocale());
}

public void sendEmailChange(Request req, Account self){
Account.ActivationInfo info=self.activationInfo;
if(info==null)
throw new IllegalArgumentException("This account is already activated");
if(info.emailState!=Account.ActivationInfo.EmailConfirmationState.CHANGE_PENDING)
throw new IllegalArgumentException("Unexpected email state "+info.emailState);
if(info.emailConfirmationKey==null)
throw new IllegalArgumentException("No email confirmation key");

Lang l=Utils.lang(req);
String link=UriBuilder.local().path("account", "activate").queryParam("key", info.emailConfirmationKey).build().toString();
String plaintext=l.get("email_change_new_body_plain", Map.of("name", self.user.firstName, "serverName", Config.domain, "newAddress", self.getUnconfirmedEmail(), "oldAddress", self.email))+"\n\n"+link;
send(self.getUnconfirmedEmail(), l.get("email_change_new_subject", Map.of("domain", Config.domain)), plaintext, "update_email_new", Map.of(
"name", self.user.firstName,
"gender", self.user.gender,
"confirmationLink", link,
"oldAddress", self.email,
"newAddress", self.getUnconfirmedEmail()
), l.getLocale());
}

public void sendEmailChangeDoneToPreviousAddress(Request req, Account self){
Account.ActivationInfo info=self.activationInfo;
if(info==null)
throw new IllegalArgumentException("This account is already activated");
if(info.emailState!=Account.ActivationInfo.EmailConfirmationState.CHANGE_PENDING)
throw new IllegalArgumentException("Unexpected email state "+info.emailState);
if(info.emailConfirmationKey==null)
throw new IllegalArgumentException("No email confirmation key");

Lang l=Utils.lang(req);
String plaintext=l.get("email_change_old_body", Map.of("name", self.user.firstName, "serverName", Config.domain));
send(self.email, l.get("email_change_old_subject", Map.of("domain", Config.domain)), plaintext, "update_email_old", Map.of(
"name", self.user.firstName,
"gender", self.user.gender,
"address", self.getUnconfirmedEmail()
), l.getLocale());
}

private void send(String to, String subject, String plaintext, String templateName, Map<String, Object> templateParams, Locale templateLocale){
try{
MimeMessage msg=new MimeMessage(session);
Expand Down Expand Up @@ -124,6 +182,10 @@ private void send(String to, String subject, String plaintext, String templateNa
}
}

public static String generateConfirmationKey(){
return Utils.randomAlphanumericString(64);
}

private static class SendRunnable implements Runnable{

private final MimeMessage msg;
Expand Down
11 changes: 10 additions & 1 deletion src/main/java/smithereen/SmithereenApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,10 @@ public static void main(String[] args){
post("/resetPassword", SessionRoutes::resetPassword);
get("/actuallyResetPassword", SessionRoutes::actuallyResetPasswordForm);
post("/actuallyResetPassword", SessionRoutes::actuallyResetPassword);
getWithCSRF("/resendConfirmationEmail", SessionRoutes::resendEmailConfirmation);
getLoggedIn("/changeEmailForm", SessionRoutes::changeEmailForm);
postWithCSRF("/changeEmail", SessionRoutes::changeEmail);
getLoggedIn("/activate", SessionRoutes::activateAccount);
});

path("/settings", ()->{
Expand All @@ -199,6 +203,9 @@ public static void main(String[] args){
postWithCSRF("/blockDomain", SettingsRoutes::blockDomain);
getLoggedIn("/confirmUnblockDomain", SettingsRoutes::confirmUnblockDomain);
postWithCSRF("/unblockDomain", SettingsRoutes::unblockDomain);
postWithCSRF("/updateEmail", SettingsRoutes::updateEmail);
getWithCSRF("/cancelEmailChange", SettingsRoutes::cancelEmailChange);
getWithCSRF("/resendEmailConfirmation", SettingsRoutes::resendEmailConfirmation);

path("/admin", ()->{
getRequiringAccessLevel("", Account.AccessLevel.ADMIN, SettingsAdminRoutes::index);
Expand All @@ -211,8 +218,10 @@ public static void main(String[] args){
postRequiringAccessLevelWithCSRF("/sendTestEmail", Account.AccessLevel.ADMIN, SettingsAdminRoutes::sendTestEmail);
getRequiringAccessLevel("/users/banForm", Account.AccessLevel.MODERATOR, SettingsAdminRoutes::banUserForm);
getRequiringAccessLevel("/users/confirmUnban", Account.AccessLevel.MODERATOR, SettingsAdminRoutes::confirmUnbanUser);
getRequiringAccessLevel("/users/confirmActivate", Account.AccessLevel.MODERATOR, SettingsAdminRoutes::confirmActivateAccount);
postRequiringAccessLevelWithCSRF("/users/ban", Account.AccessLevel.MODERATOR, SettingsAdminRoutes::banUser);
postRequiringAccessLevelWithCSRF("/users/unban", Account.AccessLevel.MODERATOR, SettingsAdminRoutes::unbanUser);
postRequiringAccessLevelWithCSRF("/users/activate", Account.AccessLevel.MODERATOR, SettingsAdminRoutes::activateAccount);
});
});

Expand Down Expand Up @@ -494,7 +503,7 @@ public static void main(String[] args){
long t=(long)l;
resp.header("X-Generated-In", (System.currentTimeMillis()-t)+"");
}
if(req.attribute("isTemplate")!=null){
if(req.attribute("isTemplate")!=null && !Utils.isAjax(req)){
String cssName=req.attribute("mobile")!=null ? "mobile" : "desktop";
resp.header("Link", "</res/"+cssName+".css?"+Utils.staticFileHash+">; rel=preload; as=style, </res/common.js?"+Utils.staticFileHash+">; rel=preload; as=script");
resp.header("Vary", "User-Agent, Accept-Language");
Expand Down
43 changes: 40 additions & 3 deletions src/main/java/smithereen/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,17 @@ public static String csrfTokenFromSessionID(byte[] sid){
return String.format(Locale.ENGLISH, "%08x%08x", v1, v2);
}

private static boolean isAllowedForRestrictedAccounts(Request req){
String path=req.pathInfo();
return List.of(
"/account/logout",
"/account/resendConfirmationEmail",
"/account/changeEmailForm",
"/account/changeEmail",
"/account/activate"
).contains(path);
}

public static boolean requireAccount(Request req, Response resp){
if(req.session(false)==null || req.session().attribute("info")==null || ((SessionInfo)req.session().attribute("info")).account==null){
String to=req.pathInfo();
Expand All @@ -124,13 +135,17 @@ public static boolean requireAccount(Request req, Response resp){
return false;
}
Account acc=sessionInfo(req).account;
if(acc.banInfo!=null){
if(acc.banInfo!=null && !isAllowedForRestrictedAccounts(req)){
Lang l=lang(req);
String msg=l.get("your_account_is_banned");
if(StringUtils.isNotEmpty(acc.banInfo.reason))
msg+="\n\n"+l.get("ban_reason")+": "+acc.banInfo.reason;
resp.body(new RenderedTemplateResponse("generic_message", req).with("message", msg).renderToString());
return false;
}else if(acc.activationInfo!=null && acc.activationInfo.emailState==Account.ActivationInfo.EmailConfirmationState.NOT_CONFIRMED && !isAllowedForRestrictedAccounts(req)){
Lang l=lang(req);
resp.body(new RenderedTemplateResponse("email_confirm_required", req).with("email", acc.email).pageTitle(l.get("account_activation")).renderToString());
return false;
}
return true;
}
Expand Down Expand Up @@ -179,7 +194,7 @@ public static Object wrapError(Request req, Response resp, String errorKey, Map<
Lang l=lang(req);
String msg=formatArgs!=null ? l.get(errorKey, formatArgs) : l.get(errorKey);
if(isAjax(req)){
return new WebDeltaResponse(resp).messageBox(l.get("error"), msg, l.get("ok"));
return new WebDeltaResponse(resp).messageBox(l.get("error"), msg, l.get("close"));
}
return new RenderedTemplateResponse("generic_error", req).with("error", msg).with("back", back(req)).with("title", l.get("error"));
}
Expand All @@ -192,7 +207,7 @@ public static String wrapErrorString(Request req, Response resp, String errorKey
Lang l=lang(req);
String msg=formatArgs!=null ? l.get(errorKey, formatArgs) : l.get(errorKey);
if(isAjax(req)){
return new WebDeltaResponse(resp).messageBox(l.get("error"), msg, l.get("ok")).json();
return new WebDeltaResponse(resp).messageBox(l.get("error"), msg, l.get("close")).json();
}else if(isActivityPub(req)){
return msg;
}
Expand Down Expand Up @@ -821,6 +836,28 @@ public static void requireQueryParams(Request req, String... params){
}
}

public static String substituteLinks(String str, Map<String, Object> links){
Element root=Jsoup.parseBodyFragment(str).body();
for(String id:links.keySet()){
Element link=root.getElementById(id);
if(link==null)
continue;
link.removeAttr("id");
//noinspection unchecked
Map<String, Object> attrs=(Map<String, Object>) links.get(id);
for(String attr:attrs.keySet()){
Object value=attrs.get(attr);
if(attr.equals("_")){
link.tagName(value.toString());
}else if(value instanceof Boolean b)
link.attr(attr, b);
else if(value instanceof String s)
link.attr(attr, s);
}
}
return root.html();
}

public interface MentionCallback{
User resolveMention(String username, String domain);
User resolveMention(String uri);
Expand Down
36 changes: 35 additions & 1 deletion src/main/java/smithereen/data/Account.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.Instant;

import smithereen.Utils;
Expand All @@ -20,6 +19,7 @@ public class Account{
public Instant createdAt;
public Instant lastActive;
public BanInfo banInfo;
public ActivationInfo activationInfo;

public User invitedBy; // used in admin UIs

Expand All @@ -33,6 +33,8 @@ public String toString(){
", prefs="+prefs+
", createdAt="+createdAt+
", lastActive="+lastActive+
", banInfo="+banInfo+
", activationInfo="+activationInfo+
", invitedBy="+invitedBy+
'}';
}
Expand All @@ -58,9 +60,30 @@ public static Account fromResultSet(ResultSet res) throws SQLException{
acc.prefs=new UserPreferences();
}
}
String activation=res.getString("activation_info");
if(activation!=null)
acc.activationInfo=Utils.gson.fromJson(activation, ActivationInfo.class);
return acc;
}

public String getUnconfirmedEmail(){
if(activationInfo==null)
return null;
return switch(activationInfo.emailState){
case NOT_CONFIRMED -> email;
case CHANGE_PENDING -> activationInfo.newEmail;
};
}

public String getCurrentEmailMasked(){
String[] parts=email.split("@", 2);
if(parts.length!=2)
return email;
String user=parts[0];
int count=user.length()<5 ? 1 : 2;
return user.substring(0, count)+"*".repeat(user.length()-count)+"@"+parts[1];
}

public enum AccessLevel{
BANNED,
REGULAR,
Expand All @@ -73,4 +96,15 @@ public static class BanInfo{
public String reason;
public Instant when;
}

public static class ActivationInfo{
public String emailConfirmationKey;
public String newEmail;
public EmailConfirmationState emailState;

public enum EmailConfirmationState{
NOT_CONFIRMED,
CHANGE_PENDING
}
}
}
12 changes: 12 additions & 0 deletions src/main/java/smithereen/data/WebDeltaResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ public WebDeltaResponse runScript(@NotNull String script){
return this;
}

public WebDeltaResponse keepBox(){
commands.add(new KeepBoxCommand());
return this;
}

public String json(){
return Utils.gson.toJson(commands);
}
Expand Down Expand Up @@ -308,4 +313,11 @@ public RunScriptCommand(String script){
this.script=script;
}
}

public static class KeepBoxCommand extends Command{

public KeepBoxCommand(){
super("kb");
}
}
}
Loading

0 comments on commit 405f3ff

Please sign in to comment.