Pārlūkot izejas kodu

Add useHttps for custom domains

poeti8 7 gadi atpakaļ
vecāks
revīzija
ed6f77876d

+ 15 - 8
client/actions/settings.js

@@ -23,10 +23,13 @@ export const showDomainInput = () => ({ type: SHOW_DOMAIN_INPUT });
 
 export const getUserSettings = () => async dispatch => {
   try {
-    const { data: { apikey, customDomain, homepage } } = await axios.get('/api/auth/usersettings', {
-      headers: { Authorization: cookie.get('token') },
-    });
-    dispatch(setDomain({ customDomain, homepage }));
+    const { data: { apikey, customDomain, homepage, useHttps } } = await axios.get(
+      '/api/auth/usersettings',
+      {
+        headers: { Authorization: cookie.get('token') },
+      }
+    );
+    dispatch(setDomain({ customDomain, homepage, useHttps }));
     dispatch(setApiKey(apikey));
   } catch (error) {
     //
@@ -36,10 +39,14 @@ export const getUserSettings = () => async dispatch => {
 export const setCustomDomain = params => async dispatch => {
   dispatch(showDomainLoading());
   try {
-    const { data: { customDomain, homepage } } = await axios.post('/api/url/customdomain', params, {
-      headers: { Authorization: cookie.get('token') },
-    });
-    dispatch(setDomain({ customDomain, homepage }));
+    const { data: { customDomain, homepage, useHttps } } = await axios.post(
+      '/api/url/customdomain',
+      params,
+      {
+        headers: { Authorization: cookie.get('token') },
+      }
+    );
+    dispatch(setDomain({ customDomain, homepage, useHttps }));
   } catch ({ response }) {
     dispatch(setDomainError(response.data.error));
   }

+ 10 - 1
client/components/Settings/Settings.js

@@ -78,6 +78,7 @@ class Settings extends Component {
       showModal: false,
       passwordMessage: '',
       passwordError: '',
+      useHttps: null,
       ban: {
         domain: false,
         error: '',
@@ -90,6 +91,7 @@ class Settings extends Component {
     this.onSubmitBan = this.onSubmitBan.bind(this);
     this.onChangeBanCheckboxes = this.onChangeBanCheckboxes.bind(this);
     this.handleCustomDomain = this.handleCustomDomain.bind(this);
+    this.handleCheckbox = this.handleCheckbox.bind(this);
     this.deleteDomain = this.deleteDomain.bind(this);
     this.showModal = this.showModal.bind(this);
     this.closeModal = this.closeModal.bind(this);
@@ -162,9 +164,14 @@ class Settings extends Component {
   handleCustomDomain(e) {
     e.preventDefault();
     if (this.props.domainLoading) return null;
+    const { useHttps } = this.state;
     const customDomain = e.currentTarget.elements.customdomain.value;
     const homepage = e.currentTarget.elements.homepage.value;
-    return this.props.setCustomDomain({ customDomain, homepage });
+    return this.props.setCustomDomain({ customDomain, homepage, useHttps });
+  }
+
+  handleCheckbox({ target: { id, checked } }) {
+    this.setState({ [id]: !checked });
   }
 
   deleteDomain() {
@@ -236,6 +243,8 @@ class Settings extends Component {
         )}
         <SettingsDomain
           handleCustomDomain={this.handleCustomDomain}
+          handleCheckbox={this.handleCheckbox}
+          useHttps={this.state.useHttps}
           loading={this.props.domainLoading}
           settings={this.props.settings}
           showDomainInput={this.props.showDomainInput}

+ 21 - 2
client/components/Settings/SettingsDomain.js

@@ -2,6 +2,7 @@ import React from 'react';
 import PropTypes from 'prop-types';
 import styled from 'styled-components';
 import TextInput from '../TextInput';
+import Checkbox from '../Checkbox';
 import Button from '../Button';
 import Error from '../Error';
 import { fadeIn } from '../../helpers/animations';
@@ -73,7 +74,6 @@ const Homepage = styled.h6`
 const InputWrapper = styled.div`
   display: flex;
   align-items: center;
-  margin-bottom: 16px;
 `;
 
 const LabelWrapper = styled.div`
@@ -86,7 +86,15 @@ const LabelWrapper = styled.div`
   }
 `;
 
-const SettingsDomain = ({ settings, handleCustomDomain, loading, showDomainInput, showModal }) => (
+const SettingsDomain = ({
+  settings,
+  handleCustomDomain,
+  loading,
+  showDomainInput,
+  showModal,
+  useHttps,
+  handleCheckbox,
+}) => (
   <div>
     <h3>Custom domain</h3>
     <p>
@@ -102,6 +110,7 @@ const SettingsDomain = ({ settings, handleCustomDomain, loading, showDomainInput
           <Domain>
             <span>{settings.customDomain}</span>
           </Domain>
+          {settings.useHttps && <Homepage>(With HTTPS)</Homepage>}
           <Homepage>
             (Homepage redirects to <span>{settings.homepage || window.location.hostname}</span>)
           </Homepage>
@@ -144,6 +153,14 @@ const SettingsDomain = ({ settings, handleCustomDomain, loading, showDomainInput
             />
           </LabelWrapper>
         </InputWrapper>
+        <Checkbox
+          checked={useHttps === null ? settings.useHttps : useHttps}
+          id="useHttps"
+          name="useHttps"
+          onClick={handleCheckbox}
+          withMargin={false}
+          label="Use HTTPS (We don't handle the SSL, you should take care of it)"
+        />
         <Button type="submit" color="purple" icon={loading ? 'loader' : ''}>
           Set domain
         </Button>
@@ -161,6 +178,8 @@ SettingsDomain.propTypes = {
   loading: PropTypes.bool.isRequired,
   showDomainInput: PropTypes.func.isRequired,
   showModal: PropTypes.func.isRequired,
+  handleCheckbox: PropTypes.func.isRequired,
+  useHttps: PropTypes.bool.isRequired,
 };
 
 export default SettingsDomain;

+ 2 - 0
client/reducers/settings.js

@@ -11,6 +11,7 @@ const initialState = {
   customDomain: '',
   homepage: '',
   domainInput: true,
+  useHttps: false,
 };
 
 const settings = (state = initialState, action) => {
@@ -21,6 +22,7 @@ const settings = (state = initialState, action) => {
         customDomain: action.payload.customDomain,
         homepage: action.payload.homepage,
         domainInput: false,
+        useHttps: action.payload.useHttps,
       };
     case SET_APIKEY:
       return { ...state, apikey: action.payload };

+ 1 - 0
server/controllers/authController.js

@@ -174,6 +174,7 @@ exports.userSettings = ({ user }, res) =>
     apikey: user.apikey || '',
     customDomain: user.domain || '',
     homepage: user.homepage || '',
+    useHttps: user.useHttps || false,
   });
 
 exports.requestPasswordReset = async ({ body: { email } }, res) => {

+ 13 - 6
server/controllers/urlController.js

@@ -65,7 +65,7 @@ exports.urlShortener = async ({ body, user }, res) => {
         ...url,
         password: !!url.password,
         reuse: true,
-        shortUrl: generateShortUrl(url.id, user.domain),
+        shortUrl: generateShortUrl(url.id, user.domain, user.useHttps),
       };
       return res.json(data);
     }
@@ -241,11 +241,18 @@ exports.setCustomDomain = async ({ body, user }, res) => {
       .status(400)
       .json({ error: 'Domain is already taken. Contact us for multiple users.' });
   }
-  const userCustomDomain = await setCustomDomain({ user, customDomain, homepage });
+  const userCustomDomain = await setCustomDomain({
+    user,
+    customDomain,
+    homepage,
+    useHttps: body.useHttps,
+  });
   if (userCustomDomain)
-    return res
-      .status(201)
-      .json({ customDomain: userCustomDomain.name, homepage: userCustomDomain.homepage });
+    return res.status(201).json({
+      customDomain: userCustomDomain.name,
+      homepage: userCustomDomain.homepage,
+      useHttps: userCustomDomain.useHttps,
+    });
   return res.status(400).json({ error: "Couldn't set custom domain." });
 };
 
@@ -263,7 +270,7 @@ exports.customDomainRedirection = async (req, res, next) => {
       preservedUrls.filter(u => u !== 'url-password').some(item => item === path.replace('/', '')))
   ) {
     const { homepage } = await getCustomDomain({ customDomain: headers.host });
-    return res.redirect(301, homepage || `http://${config.DEFAULT_DOMAIN + path}`);
+    return res.redirect(301, homepage || `https://${config.DEFAULT_DOMAIN + path}`);
   }
   return next();
 };

+ 14 - 6
server/db/url.js

@@ -44,7 +44,11 @@ exports.createShortUrl = params =>
           ...data,
           password: !!data.password,
           reuse: !!params.reuse,
-          shortUrl: generateShortUrl(data.id, params.user && params.user.domain),
+          shortUrl: generateShortUrl(
+            data.id,
+            params.user && params.user.domain,
+            params.user && params.user.useHttps
+          ),
         });
       })
       .catch(err => session.close() || reject(err));
@@ -158,7 +162,7 @@ exports.getUrls = ({ user, options, setCount }) =>
             'WITH l ORDER BY l.createdAt DESC ' +
             'WITH l SKIP $skip LIMIT $limit ' +
             `OPTIONAL MATCH (l)-[:USES]->(d) ${setVisitsCount} ` +
-            'RETURN l, d.name AS domain',
+            'RETURN l, d.name AS domain, d.useHttps as useHttps',
           {
             email: user.email,
             limit,
@@ -171,12 +175,15 @@ exports.getUrls = ({ user, options, setCount }) =>
         session.close();
         const urls = records.map(record => {
           const visitCount = record.get('l').properties.count;
+          const domain = record.get('domain');
+          const protocol = record.get('useHttps') || !domain ? 'https://' : 'http://';
           return {
             ...record.get('l').properties,
             count: typeof visitCount === 'object' ? visitCount.toNumber() : visitCount,
             password: !!record.get('l').properties.password,
-            shortUrl: `http${!record.get('domain') ? 's' : ''}://${record.get('domain') ||
-              config.DEFAULT_DOMAIN}/${record.get('l').properties.id}`,
+            shortUrl: `${protocol}${domain || config.DEFAULT_DOMAIN}/${
+              record.get('l').properties.id
+            }`,
           };
         });
         resolve({ list: urls });
@@ -209,7 +216,7 @@ exports.getCustomDomain = ({ customDomain }) =>
       .catch(err => session.close() || reject(err));
   });
 
-exports.setCustomDomain = ({ user, customDomain, homepage }) =>
+exports.setCustomDomain = ({ user, customDomain, homepage, useHttps }) =>
   new Promise((resolve, reject) => {
     const session = driver.session();
     session
@@ -217,12 +224,13 @@ exports.setCustomDomain = ({ user, customDomain, homepage }) =>
         tx.run(
           'MATCH (u:USER { email: $email }) ' +
             'OPTIONAL MATCH (u)-[r:OWNS]->() DELETE r ' +
-            `MERGE (d:DOMAIN { name: $customDomain, homepage: $homepage }) ` +
+            `MERGE (d:DOMAIN { name: $customDomain, homepage: $homepage, useHttps: $useHttps }) ` +
             'MERGE (u)-[:OWNS]->(d) RETURN u, d',
           {
             customDomain,
             homepage: homepage || '',
             email: user.email,
+            useHttps,
           }
         )
       )

+ 2 - 1
server/db/user.js

@@ -22,7 +22,8 @@ exports.getUser = ({ email = '', apikey = '' }) =>
         const domainProps = res.records.length && res.records[0].get('l');
         const domain = domainProps ? domainProps.properties.name : '';
         const homepage = domainProps ? domainProps.properties.homepage : '';
-        return resolve(user && { ...user, domain, homepage });
+        const useHttps = domainProps ? domainProps.properties.useHttps : '';
+        return resolve(user && { ...user, domain, homepage, useHttps });
       })
       .catch(err => reject(err));
   });

+ 4 - 2
server/utils/index.js

@@ -8,8 +8,10 @@ exports.addProtocol = url => {
   return hasProtocol ? url : `http://${url}`;
 };
 
-exports.generateShortUrl = (id, domain) =>
-  `http${!domain ? 's' : ''}://${domain || config.DEFAULT_DOMAIN}/${id}`;
+exports.generateShortUrl = (id, domain, useHttps) => {
+  const protocol = useHttps || !domain ? 'https://' : 'http://';
+  return `${protocol}${domain || config.DEFAULT_DOMAIN}/${id}`;
+};
 
 exports.isAdmin = email => config.ADMIN_EMAILS.includes(email);