Terms of Service added

This commit is contained in:
Luke Berndt
2024-06-29 07:57:35 -04:00
parent 4fde3f062d
commit ca342cd9f2
8 changed files with 172 additions and 83 deletions

View File

@@ -28,18 +28,23 @@ const local = new LocalStrategy({
});
if (!user) {
console.error("Auth Error - user not found: " + email);
return done(null, false, { message: `Invalid email or password` })
return done(null, false, { message: `Invalid email or password`, reason: "invalid" })
}
if (!user.confirmEmail) {
console.error("Auth Error - user has not confirmed email: " + email);
return done(null, false, { message: `User's email is not confirmed` })
return done(null, false, { message: `User's email is not confirmed`, reason: "unconfirmed email"})
}
/*
if (user.terms != 1.1) {
console.error("Auth Error - user has not accepted TOS: " + email);
return done(null, false, { message: `User has not accepted the Terms of Service`, reason: "unaccepted TOS"})
}*/
user.comparePassword(password, (err, isMatch) => {
if (isMatch) {
return done(null, user)
} else {
console.error("Auth Error - password mismatch: " + email);
return done(null, false, { message: "Invalid email or password" })
return done(null, false, { message: "Invalid email or password", reason: "invalid"})
}
})
})

View File

@@ -37,14 +37,16 @@ exports.authenticated = function (req, res, next) {
screenName,
location,
email,
admin
admin,
terms
}) => ({
firstName,
lastName,
screenName,
location,
email,
admin
admin,
terms
}))(
req.user
);
@@ -71,7 +73,8 @@ exports.login = function (req, res, next) {
console.log("No user");
return res.json({
success: false,
message: info.message
message: info.message,
reason: info.reason
});
}
// ***********************************************************************
@@ -99,6 +102,7 @@ exports.login = function (req, res, next) {
return res.json({
success: false,
message: "unconfirmed email",
reason: "unconfirmed email",
userId: user.id
});
});
@@ -111,14 +115,16 @@ exports.login = function (req, res, next) {
screenName,
location,
email,
admin
admin,
terms
}) => ({
firstName,
lastName,
screenName,
location,
email,
admin
admin,
terms
}))(
user
);
@@ -480,8 +486,16 @@ exports.terms = async function (req, res, next) {
});
return;
});
var clientUser = (({
terms
}) => ({
terms
}))(
user
);
res.json({
success: true
success: true,
user: clientUser
});
return;
}

View File

@@ -2,15 +2,17 @@
import React, { useEffect } from "react";
import { useSelector, useDispatch } from 'react-redux'
import { authenticateUser } from "../features/user/userSlice";
import { useNavigate } from 'react-router-dom';
import { useNavigate, useLocation } from 'react-router-dom';
/**
* Higher-order component (HOC) to wrap restricted pages
*/
const Restricted = ({ children }) => {
const { authenticated, hasAuthenticated } = useSelector((state) => state.user);
const { authenticated, hasAuthenticated,terms } = useSelector((state) => state.user);
const dispatch = useDispatch();
const navigate = useNavigate();
const location = useLocation();
const { hash, pathname, search } = location;
useEffect(() => {
dispatch(authenticateUser({}));
@@ -18,6 +20,9 @@ const Restricted = ({ children }) => {
if (hasAuthenticated) {
if (authenticated) {
if ((pathname != "/terms") && (terms != 1.1)) {
navigate("/terms")
}
return children;
}
else {

View File

@@ -1,8 +1,7 @@
import { useState } from "react";
import { useNavigate, useSearchParams, Link } from 'react-router-dom';
import { loginUser } from "../features/user/userSlice";
import { useDispatch } from 'react-redux'
import { useDispatch } from 'react-redux';
import {
Container,
Header,
@@ -15,7 +14,7 @@ import {
Divider
} from "semantic-ui-react";
// ----------------------------------------------------
const dividerStyle = {
marginTop: "40px"
};
@@ -25,7 +24,7 @@ const forgotStyle = {
textAlign: "right"
};
// ----------------------------------------------------
const Login = (props) => {
@@ -41,7 +40,7 @@ const Login = (props) => {
event.preventDefault();
const result = await dispatch(loginUser({email,password})).unwrap();
if (result.success) {
if (nextLocation) {
switch (nextLocation) {
case "frontend":
@@ -62,11 +61,15 @@ const Login = (props) => {
} else {
console.error(result);
setLoginMessage(result.message);
if (result.message === "unconfirmed email") {
if (result.reason === "unconfirmed email") {
/*data.userId = response.data.userId;
dispatch(loginEmailError(data));*/
navigate("/wait-confirm-email");
}
}
/*
if (result.reason === "unaccepted TOS") {
navigate("/terms");
}*/
}
}

View File

@@ -1,15 +1,22 @@
import React, { useState } from "react";
import { Segment, Container, Button } from "semantic-ui-react";
import { acceptTerms } from "../features/user/userSlice";
import { Segment, Container, Button, Header } from "semantic-ui-react";
import { useDispatch, useSelector } from 'react-redux'
import { useParams, useNavigate } from 'react-router-dom';
const Terms = ({ user, acceptTerms }) => {
const Terms = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const user = useSelector((state) => state.user);
const [requestMessage, setRequestMessage] = useState(null);
const handleAccept = () => {
acceptTerms(user.userId).then((requestMessage) => {
if (requestMessage) {
setRequestMessage(requestMessage);
}
});
const handleAccept = async () => {
const result = await dispatch(acceptTerms(user)).unwrap();
if (result.success) {
navigate("/profile");
} else {
console.error(result);
}
};
const handleDecline = () => {};
@@ -18,43 +25,98 @@ const Terms = ({ user, acceptTerms }) => {
<div>
<Container text>
<Segment attached>
The agreement is useful to The Guardian to set the rules and guidelines for visitors, users and customers:
<Header as='h1'>Terms of Service</Header>
<em>Last updated: June 28, 2024</em>
Registration: inform users that they must agree to a set of rules when they register on your website or mobile app
Disable or terminate accounts: inform users that their accounts can be disabled or terminated if abuses happen on your website or mobile app, e.g. spamming, swearing etc.
Owner of content: notify users that you, the company, is the owner of the content appearing on your website or mobile app – except in cases where other users can post content (upload, share, create etc.) where the users are the owners of such content
Changes to the agreement: users should be informed about upcoming changes to the agreement before the changes are applied.
DMCA: The Guardian is also informing owners of copyright content that they can submit a DMCA notice to the website if they found infringing content in their DMCA clause.
And more
These are only a few examples of what kind of clauses and disclosures you can add in this kind of agreement.
<Header as='h2'>Introduction</Header>
What is a Terms of Use
While a Terms of Use is recommended to have, it’s not mandatory by law as the Privacy Policy agreement is required.
Welcome and thank you for using OpenMHz. While it has been written and maintained by one person, the community around OpenMHz is very generous in contributing code and recordings. The goal of the Terms of Service is to help better define the relationship between OpenMHz and the people who use it.
This agreement serves a single purpose: to inform users of rules that they must agree to. A Privacy Policy must inform users about your privacy practices (what data you collect, why, how, etc.)
When we say “Company”, "I", "me", “we”, “our”, or “us” in this document, we are referring to Robotastic, which is a Sole Proprietorship based in Washington, DC.
The definition of a Terms of Use agreement is simple:
When we say “Services”, we mean any product created and maintained by Robotastic. That includes the OpenMHz.com website, content, API, and podcast, whether delivered within a web browser, desktop application, mobile application, or another format.
Terms of service (also known as terms of use and terms and conditions, commonly abbreviated as ToS or TOS and TOU) are rules which one must agree to abide by in order to use a service.
When we say “You” or “your”, we are referring to the people or organizations that own an account with one or more of our Services.
Wikipedia
Because this agreement simply acts as a contract between you, the company, and the users using or accessing your website or mobile app, the agreement can be named as you’d like:
We may update these Terms of Service ("Terms") in the future. You can track all changes made since mid-2024 on GitHub. Typically these changes have been to clarify some of these terms by linking to an expanded related policy. Whenever we make a significant change to our policies, we will refresh the date at the top of this page and take any other appropriate steps to notify account holders.
Terms of Use (ToU)
Terms of Service (ToS)
Terms and Conditions (T&amp;C)
User Agreement
Conditions of Use
And so on
The “Conditions of Use” of Amazon could be named “Terms of Use”:
When you use our Services, now or in the future, you are agreeing to the latest Terms. There may be times where we do not exercise or enforce a right or provision of the Terms; however, that does not mean we are waiving that right or provision. These Terms do contain a limitation of our liability.
If you violate any of the Terms, we may terminate your account. That’s a broad statement and it means you need to place a lot of trust in us.
<Header as='h2'>1. Use of Services</Header>
OpenMHz provides access to live and archived audio from public safety radio systems, as well as associated metadata and statistics ("Content"). You may access this content through the web interface and iOS app. Additionally, you may contribute Content to OpenMHz using our API.
<Header as='h2'>2. Contributing Content</Header>
By contributing Content to OpenMHz, you grant OpenMHz a perpetual, irrevocable, worldwide, royalty-free license to distribute, sublicense, and otherwise make the Content available to others.
<Header as='h2'>3. Reuse of Content</Header>
You may reuse Content obtained from the OpenMHz website and app, subject to the following conditions:
- Credit is given to OpenMHz, if it is feasible for the type of distribution medium and it doesn't make things look ugly for You.
- The Content is not provided en masse to others via scraping or other means.
- The Content is not used to misrepresent a situation or for any unlawful purpose.
- The Content is hosted by You and does not make use of OpenMHz's infrastructure. Linking to the OpenMHz website is acceptable, linking directly to the audio files is not.
<Header as='h2'>4. No Resale of Services</Header>
You agree not to sell, resell, or offer for any commercial purposes, any portion of OpenMHz. This includes the wrapping of the OpenMHz website as a mobile or desktop app.
<Header as='h2'>5. API Access</Header>
The OpenMHz API is designed solely to support the OpenMHz website frontend and iOS app. You may not use the API for any other purpose without explicit permission from OpenMHz. The infrastructure for OpenMHz has not been well designed, and unauthorized use may result in the whole thing crashing. Additionally, some API calls may place a significant load on the server, and preplanning is required to avoid this. We will allow for API access on a case-by-case basis. Please contact us at: luke@robotastic.com
<Header as='h2'>6. Legal Compliance</Header>
Contributors are solely responsible for ensuring that the contributed Content provided to OpenMHz complies with all applicable laws and regulations. OpenMHz is not liable for any Content that violates legal statutes.
<Header as='h2'>7. Right to Remove Content</Header>
OpenMHz reserves the right to remove any recordings from our Services at our discretion, for any reason, without prior notice.
<Header as='h2'>8. No Guarantees</Header>
OpenMHz does not guarantee that our Services will be available at all times or that they will function without interruption or errors. We provide our Services “as is” and “as available,” without any warranties of any kind, either express or implied. While we strive to provide accurate information, we do not guarantee that the Content on our Services is complete, accurate, or up-to-date. There is a good chance that at some point in time I will mess up and accidentally delete the database for the website and associated recordings. You should not assume that contributed Content will be reliably maintained.
<Header as='h2'>9. Limitation of Liability</Header>
To the fullest extent permitted by law, OpenMHz shall not be liable for any direct, indirect, incidental, special, consequential, or punitive damages arising out of your use of, or inability to use, our Services.
<Header as='h2'>10. Amendments</Header>
OpenMHz reserves the right to amend these Terms at any time. Your continued use of our Services following any changes indicates your acceptance of the new Terms.
<Header as='h2'>11. OpenMHz Intellectual Property</Header>
All intellectual property rights in the Services, including trademarks, logos, and any content created by OpenMHz, are owned by Robotastic. You may use the OpenMHz name to describe where content was obtained from. Do not you use the OpenMHz name in a way that implies endorsement or affiliation with OpenMHz. Do not call your App OpenMHz.
<Header as='h2'>12. Indemnification</Header>
You agree to indemnify, defend, and hold harmless OpenMHz, its officers, directors, employees, and agents from and against any claims, liabilities, damages, losses, and expenses, including reasonable attorney’s fees, arising out of or in any way connected with your access to or use of the Services or your violation of these Terms.
<Header as='h2'>13. Be Nice</Header>
The Services are provided as a labor of love by a single individual. I have a limited amount of time to work on the Services and funding to support the infrastructure. Please keep this in mind as you use the Services and act respectfully.
<Header as='h2'>14. Contact Me</Header>
If you have any questions or concerns about these Terms, please contact me at luke@robotastic.com.
By using OpenMHz, you acknowledge that you have read, understood, and agree to be bound by these Terms of Service. Thank you for using our Services!
These Terms were written to help set expectations around usage. If you think something is missing or could be better stated, please reach out.
</Segment>
<Button.Group attached="bottom">
<Button onClick={handleDecline}>Decline</Button>
<Button.Or />
<Button positive onClick={handleAccept}>
Accept
</Button>
</Button.Group>
{user.terms !== 1.1 && (
<Button.Group attached="bottom">
<Button onClick={handleDecline}>Decline</Button>
<Button.Or />
<Button positive onClick={handleAccept}>
Accept
</Button>
</Button.Group>
)}
</Container>
</div>
);

View File

@@ -2,6 +2,7 @@ import {
createSlice,
createAsyncThunk
} from '@reduxjs/toolkit'
//import { terms } from '../../../server/controllers/users';
export const authenticateUser = createAsyncThunk(
@@ -102,6 +103,22 @@ export const confirmEmail = createAsyncThunk(
}
)
export const acceptTerms = createAsyncThunk(
'user/acceptTerms',
async (user) => {
const url = process.env.REACT_APP_ACCOUNT_SERVER + "/users/" + user.userId + "/terms/";
const res = await fetch(url, {
method: 'POST',
credentials: "include",
headers: {
"Content-Type": "application/json",
}
}).then(
(data) => data.json()
)
return res;
}
)
export const sendConfirmEmail = createAsyncThunk(
@@ -172,7 +189,8 @@ export const userSlice = createSlice({
firstName: "",
lastName: "",
location: "",
screenName: ""
screenName: "",
terms: 0,
},
reducers: {
@@ -197,6 +215,7 @@ export const userSlice = createSlice({
state.lastName = payload.user.lastName;
state.location = payload.user.location;
state.screenName = payload.user.screenName;
state.terms = payload.user.terms;
} else {
state.authenticated = false;
state.hasAuthenticated = true;
@@ -215,6 +234,12 @@ export const userSlice = createSlice({
state.authenticated = false;
state.hasAuthenticated = true;
},
[acceptTerms.fulfilled]: (state, data) => {
const {payload} = data;
if (payload.success) {
state.terms = payload.user.terms;
}
},
[authenticateUser.fulfilled]: (state, data) => {
const {payload} = data;
if (payload.success) {
@@ -227,6 +252,7 @@ export const userSlice = createSlice({
state.lastName = payload.user.lastName;
state.location = payload.user.location;
state.screenName = payload.user.screenName;
state.terms = payload.user.terms;
} else {
state.authenticated = false;
state.hasAuthenticated = true;

View File

@@ -3390,17 +3390,6 @@ connect-mongo@5.0.0:
debug "^4.3.1"
kruptein "^3.0.0"
connected-react-router@6.9.3:
version "6.9.3"
resolved "https://registry.yarnpkg.com/connected-react-router/-/connected-react-router-6.9.3.tgz#72300aca9f9d6f38e1f4a2901572faa02adec972"
integrity sha512-4ThxysOiv/R2Dc4Cke1eJwjKwH1Y51VDwlOrOfs1LjpdYOVvCNjNkZDayo7+sx42EeGJPQUNchWkjAIJdXGIOQ==
dependencies:
lodash.isequalwith "^4.4.0"
prop-types "^15.7.2"
optionalDependencies:
immutable "^3.8.1 || ^4.0.0"
seamless-immutable "^7.1.3"
console-control-strings@^1.0.0, console-control-strings@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
@@ -5244,11 +5233,6 @@ immer@^9.0.16, immer@^9.0.7:
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176"
integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==
"immutable@^3.8.1 || ^4.0.0":
version "4.3.0"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.0.tgz#eb1738f14ffb39fd068b1dbe1296117484dd34be"
integrity sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==
import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
@@ -6425,11 +6409,6 @@ lodash.debounce@^4.0.8:
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
lodash.isequalwith@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.isequalwith/-/lodash.isequalwith-4.4.0.tgz#266726ddd528f854f21f4ea98a065606e0fbc6b0"
integrity sha512-dcZON0IalGBpRmJBmMkaoV7d3I80R2O+FrzsZyHdNSFrANq/cgDqKQNmAHE8UEj4+QYWwwhkQOVdLHiAopzlsQ==
lodash.memoize@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@@ -8454,11 +8433,6 @@ schema-utils@^4.0.0:
ajv-formats "^2.1.1"
ajv-keywords "^5.0.0"
seamless-immutable@^7.1.3:
version "7.1.4"
resolved "https://registry.yarnpkg.com/seamless-immutable/-/seamless-immutable-7.1.4.tgz#6e9536def083ddc4dea0207d722e0e80d0f372f8"
integrity sha512-XiUO1QP4ki4E2PHegiGAlu6r82o5A+6tRh7IkGGTVg/h+UoeX4nFBeCGPOhb4CYjvkqsfm/TUtvOMYC1xmV30A==
select-hose@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"

View File

@@ -22,7 +22,7 @@ services:
- ${PWD}/data/certbot/conf/:/etc/letsencrypt/:ro
depends_on:
- backend
- frontend
- frontend
- account
- admin
command: /bin/bash -c "envsubst '$$DOMAIN_NAME' < /etc/nginx/conf.d/site.template > /etc/nginx/conf.d/default.conf && while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g 'daemon off;'"