Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
grishka committed Aug 2, 2022
2 parents 13ad484 + a464267 commit 9123190
Show file tree
Hide file tree
Showing 45 changed files with 1,260 additions and 192 deletions.
4 changes: 2 additions & 2 deletions 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.1</version>
<version>0.4.2</version>
<build>
<plugins>

Expand Down Expand Up @@ -229,7 +229,7 @@
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.9.3</version>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>io.pebbletemplates</groupId>
Expand Down
28 changes: 25 additions & 3 deletions schema.sql
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
-- MySQL dump 10.13 Distrib 8.0.27, for macos12.0 (arm64)
-- MySQL dump 10.13 Distrib 8.0.28, for macos12.2 (arm64)
--
-- Host: localhost Database: smithereen
-- ------------------------------------------------------
-- Server version 8.0.27
-- Server version 8.0.28
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;

--
Expand All @@ -20,8 +20,11 @@ CREATE TABLE `accounts` (
`preferences` text,
`last_active` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`ban_info` text,
`activation_info` json DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`),
KEY `user_id` (`user_id`),
KEY `invited_by` (`invited_by`),
CONSTRAINT `accounts_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Expand Down Expand Up @@ -422,11 +425,30 @@ CREATE TABLE `signup_invitations` (
`owner_id` int unsigned DEFAULT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`signups_remaining` int unsigned NOT NULL,
`email` varchar(200) DEFAULT NULL,
`extra` json DEFAULT NULL,
`id` int unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`code`),
UNIQUE KEY `id` (`id`),
UNIQUE KEY `email` (`email`),
KEY `owner_id` (`owner_id`),
CONSTRAINT `signup_invitations_ibfk_1` FOREIGN KEY (`owner_id`) REFERENCES `accounts` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

--
-- Table structure for table `signup_requests`
--

CREATE TABLE `signup_requests` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`email` varchar(200) NOT NULL,
`first_name` varchar(100) NOT NULL,
`last_name` varchar(100) DEFAULT NULL,
`reason` text NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

--
-- Table structure for table `users`
--
Expand Down Expand Up @@ -500,4 +522,4 @@ CREATE TABLE `wall_posts` (

/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;

-- Dump completed on 2022-05-22 11:21:31
-- Dump completed on 2022-07-29 5:01:59
18 changes: 18 additions & 0 deletions src/main/java/smithereen/Mailer.java
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,24 @@ public void sendEmailChangeDoneToPreviousAddress(Request req, Account self){
), l.getLocale());
}

public void sendSignupInvitation(Request req, Account self, String email, String code, String firstName, boolean isRequest){
Lang l=Utils.lang(req);
String link=UriBuilder.local().path("account", "register").queryParam("invite", code).build().toString();
String plaintext=Utils.stripHTML(l.get(isRequest ? "email_invite_body_start_approved" : "email_invite_body_start", Map.of(
"name", firstName,
"inviterName", self.user.getFullName(),
"serverName", Config.serverDisplayName
)));
plaintext+="\n\n"+l.get("email_invite_body_end_plain")+"\n\n"+link;
send(email, l.get(isRequest ? "email_invite_subject_approved" : "email_invite_subject", Map.of("serverName", Config.serverDisplayName)), plaintext, "signup_invitation", Map.of(
"name", firstName,
"inviterName", self.user.getFullName(),
"serverName", Config.serverDisplayName,
"inviteLink", link,
"isRequest", isRequest
), 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
12 changes: 11 additions & 1 deletion src/main/java/smithereen/MicroFormatAwareHTMLWhitelist.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
import org.jsoup.nodes.Element;
import org.jsoup.safety.Whitelist;

import java.net.IDN;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

import smithereen.data.UriBuilder;

@SuppressWarnings("deprecation")
public class MicroFormatAwareHTMLWhitelist extends Whitelist{
Expand All @@ -17,6 +21,7 @@ public class MicroFormatAwareHTMLWhitelist extends Whitelist{
"u-url",
"mention", "hashtag", "invisible"
);
private static final Pattern NON_IDN_CHAR_REGEX=Pattern.compile("[^a-z\\d.:-]", Pattern.CASE_INSENSITIVE);

public MicroFormatAwareHTMLWhitelist(){
addTags("a", "b", "i", "u", "s", "code", "p", "em", "strong", "span", "sarcasm", "sub", "sup", "br", "pre");
Expand All @@ -32,8 +37,13 @@ protected boolean isSafeAttribute(String tagName, Element el, Attribute attr){
if(attr.getKey().equals("href") && super.isSafeAttribute(tagName, el, attr)){
try{
URI uri=new URI(attr.getValue());
if(uri.getHost()==null)
if(uri.getAuthority()==null)
return false;
String authority=uri.getAuthority();
if(NON_IDN_CHAR_REGEX.matcher(authority).find()){
uri=new UriBuilder(uri).authority(IDN.toASCII(authority)).build();
attr.setValue(uri.toString());
}
// if(!Config.isLocal(uri)){
// el.attr("target", "_blank");
// }
Expand Down
17 changes: 16 additions & 1 deletion src/main/java/smithereen/SmithereenApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ public static void main(String[] args){
getLoggedIn("/changeEmailForm", SessionRoutes::changeEmailForm);
postWithCSRF("/changeEmail", SessionRoutes::changeEmail);
getLoggedIn("/activate", SessionRoutes::activateAccount);
post("/requestInvite", SessionRoutes::requestSignupInvite);
});

path("/settings", ()->{
Expand All @@ -206,6 +207,17 @@ public static void main(String[] args){
postWithCSRF("/updateEmail", SettingsRoutes::updateEmail);
getWithCSRF("/cancelEmailChange", SettingsRoutes::cancelEmailChange);
getWithCSRF("/resendEmailConfirmation", SettingsRoutes::resendEmailConfirmation);
path("/invites", ()->{
getLoggedIn("", SettingsRoutes::invites);
getLoggedIn("/createEmailInviteForm", SettingsRoutes::createEmailInviteForm);
postWithCSRF("/createEmailInvite", SettingsRoutes::createEmailInvite);
getWithCSRF("/:id/resendEmail", SettingsRoutes::resendEmailInvite);
getWithCSRF("/:id/delete", SettingsRoutes::deleteInvite);
postWithCSRF("/:id/delete", SettingsRoutes::deleteInvite);
getLoggedIn("/createInviteLinkForm", SettingsRoutes::createInviteLinkForm);
postWithCSRF("/createInviteLink", SettingsRoutes::createInviteLink);
getLoggedIn("/invitedUsers", SettingsRoutes::invitedUsers);
});

path("/admin", ()->{
getRequiringAccessLevel("", Account.AccessLevel.ADMIN, SettingsAdminRoutes::index);
Expand All @@ -222,6 +234,8 @@ public static void main(String[] args){
postRequiringAccessLevelWithCSRF("/users/ban", Account.AccessLevel.MODERATOR, SettingsAdminRoutes::banUser);
postRequiringAccessLevelWithCSRF("/users/unban", Account.AccessLevel.MODERATOR, SettingsAdminRoutes::unbanUser);
postRequiringAccessLevelWithCSRF("/users/activate", Account.AccessLevel.MODERATOR, SettingsAdminRoutes::activateAccount);
getRequiringAccessLevel("/signupRequests", Account.AccessLevel.ADMIN, SettingsAdminRoutes::signupRequests);
postRequiringAccessLevelWithCSRF("/signupRequests/:id/respond", Account.AccessLevel.ADMIN, SettingsAdminRoutes::respondToSignupRequest);
});
});

Expand Down Expand Up @@ -585,7 +599,8 @@ private static Object indexPage(Request req, Response resp){
return new RenderedTemplateResponse("index", req).with("title", Config.serverDisplayName)
.with("signupMode", Config.signupMode)
.with("serverDisplayName", Config.serverDisplayName)
.with("serverDescription", Config.serverDescription);
.with("serverDescription", Config.serverDescription)
.addNavBarItem(Utils.lang(req).get("index_welcome"));
}

private static Object methodNotAllowed(Request req, Response resp){
Expand Down
64 changes: 64 additions & 0 deletions src/main/java/smithereen/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.IDN;
import java.net.InetAddress;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.sql.SQLException;
import java.time.Instant;
import java.time.LocalDate;
Expand All @@ -45,6 +47,7 @@
import java.util.TimeZone;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
Expand All @@ -58,6 +61,7 @@
import smithereen.data.User;
import smithereen.data.WebDeltaResponse;
import smithereen.exceptions.BadRequestException;
import smithereen.exceptions.FormValidationException;
import smithereen.exceptions.UserActionNotAllowedException;
import smithereen.lang.Lang;
import smithereen.storage.GroupStorage;
Expand Down Expand Up @@ -225,6 +229,41 @@ public static Object wrapForm(Request req, Response resp, String templateName, S
}
}

public static Object wrapForm(Request req, Response resp, String templateName, String formAction, String title, String buttonKey, String formID, List<String> fieldNames, Function<String, String> fieldValueGetter, String message){
if(isAjax(req)){
WebDeltaResponse wdr=new WebDeltaResponse(resp);
if(StringUtils.isNotEmpty(message)){
wdr.keepBox().show("formMessage_"+formID).setContent("formMessage_"+formID, escapeHTML(message));
}
return wdr;
}
RenderedTemplateResponse model=new RenderedTemplateResponse(templateName, req);
if(StringUtils.isNotEmpty(message)){
model.with(formID+"Message", message);
}
if(fieldValueGetter==null){
fieldValueGetter=req::queryParams;
}
for(String name:fieldNames){
model.with(name, fieldValueGetter.apply(name));
}
return wrapForm(req, resp, templateName, formAction, title, buttonKey, model);
}

public static String requireFormField(Request req, String field, String errorKey){
String value=req.queryParams(field);
if(StringUtils.isEmpty(value))
throw new FormValidationException(lang(req).get(errorKey));
return value;
}

public static String requireFormFieldLength(Request req, String field, int minLength, String errorKey){
String value=requireFormField(req, field, errorKey);
if(value.length()<minLength)
throw new FormValidationException(lang(req).get(errorKey));
return value;
}

public static Locale localeForRequest(Request req){
SessionInfo info=sessionInfo(req);
if(info!=null){
Expand Down Expand Up @@ -424,6 +463,10 @@ public static String escapeHTML(String s){
return HtmlEscape.escapeHtml4Xml(s);
}

public static String stripHTML(String s){
return new Cleaner(Whitelist.none()).clean(Jsoup.parseBodyFragment(s)).body().html();
}

public static boolean isMobileUserAgent(String ua){
ua=ua.toLowerCase();
return ua.matches("(?i).*((android|bb\\d+|meego).+mobile|avantgo|bada\\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\\.(browser|link)|vodafone|wap|windows ce|xda|xiino).*")
Expand Down Expand Up @@ -754,6 +797,12 @@ public static String randomAlphanumericString(int length){
return new String(chars);
}

public static byte[] randomBytes(int length){
byte[] res=new byte[length];
rand.nextBytes(res);
return res;
}

public static String convertIdnToAsciiIfNeeded(String domain) throws IllegalArgumentException{
if(domain==null)
return null;
Expand Down Expand Up @@ -858,6 +907,21 @@ else if(value instanceof String s)
return root.html();
}

public static InetAddress getRequestIP(Request req){
String forwardedFor=req.headers("X-Forwarded-For");
String ip;
if(StringUtils.isNotEmpty(forwardedFor)){
ip=forwardedFor.split(",")[0].trim();
}else{
ip=req.ip();
}
try{
return InetAddress.getByName(ip);
}catch(UnknownHostException e){
throw new RuntimeException(e); // should never happen
}
}

public interface MentionCallback{
User resolveMention(String username, String domain);
User resolveMention(String uri);
Expand Down
Loading

0 comments on commit 9123190

Please sign in to comment.