diff --git a/docs/api/auth.md b/docs/api/auth.md index 39fcd09..aa11975 100644 --- a/docs/api/auth.md +++ b/docs/api/auth.md @@ -24,7 +24,8 @@ see [this JSON schema](../schema/sendmaillogincoderequest.md) ``` { "mail": "test@timelimit.io", - "locale": "de" + "locale": "de", + "deviceAuthToken": "1234abcde" } ``` @@ -34,6 +35,8 @@ If the request body is malformed or the mail address is invalid: HTTP status cod If the rate limit was exceeded: HTTP status code 429 Too Many Requests +If a deviceAuthToken was sent which is invalid: HTTP status code 401 Unauthorized + If a whitelist was configured and the mail address is not within it: ``{"mailAddressNotWhitelisted": true}`` If a blacklist was configured and the mail server is within it: ``{"mailServerBlacklisted": true}`` diff --git a/docs/schema/SendMailLoginCodeRequest.schema.json b/docs/schema/SendMailLoginCodeRequest.schema.json index d542005..57d76bc 100644 --- a/docs/schema/SendMailLoginCodeRequest.schema.json +++ b/docs/schema/SendMailLoginCodeRequest.schema.json @@ -6,6 +6,9 @@ }, "locale": { "type": "string" + }, + "deviceAuthToken": { + "type": "string" } }, "additionalProperties": false, diff --git a/docs/schema/sendmaillogincoderequest-properties-deviceauthtoken.md b/docs/schema/sendmaillogincoderequest-properties-deviceauthtoken.md new file mode 100644 index 0000000..4ecd9e7 --- /dev/null +++ b/docs/schema/sendmaillogincoderequest-properties-deviceauthtoken.md @@ -0,0 +1,15 @@ +# Untitled string in SendMailLoginCodeRequest Schema + +```txt +https://timelimit.io/SendMailLoginCodeRequest#/properties/deviceAuthToken +``` + + + +| Abstract | Extensible | Status | Identifiable | Custom Properties | Additional Properties | Access Restrictions | Defined In | +| :------------------ | :--------- | :------------- | :---------------------- | :---------------- | :-------------------- | :------------------ | :--------------------------------------------------------------------------------------------------- | +| Can be instantiated | No | Unknown status | Unknown identifiability | Forbidden | Allowed | none | [SendMailLoginCodeRequest.schema.json*](SendMailLoginCodeRequest.schema.json "open original schema") | + +## deviceAuthToken Type + +`string` diff --git a/docs/schema/sendmaillogincoderequest.md b/docs/schema/sendmaillogincoderequest.md index 4365820..ba4ca4b 100644 --- a/docs/schema/sendmaillogincoderequest.md +++ b/docs/schema/sendmaillogincoderequest.md @@ -16,10 +16,11 @@ https://timelimit.io/SendMailLoginCodeRequest # SendMailLoginCodeRequest Properties -| Property | Type | Required | Nullable | Defined by | -| :---------------- | :------- | :------- | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------- | -| [mail](#mail) | `string` | Required | cannot be null | [SendMailLoginCodeRequest](sendmaillogincoderequest-properties-mail.md "https://timelimit.io/SendMailLoginCodeRequest#/properties/mail") | -| [locale](#locale) | `string` | Required | cannot be null | [SendMailLoginCodeRequest](sendmaillogincoderequest-properties-locale.md "https://timelimit.io/SendMailLoginCodeRequest#/properties/locale") | +| Property | Type | Required | Nullable | Defined by | +| :---------------------------------- | :------- | :------- | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [mail](#mail) | `string` | Required | cannot be null | [SendMailLoginCodeRequest](sendmaillogincoderequest-properties-mail.md "https://timelimit.io/SendMailLoginCodeRequest#/properties/mail") | +| [locale](#locale) | `string` | Required | cannot be null | [SendMailLoginCodeRequest](sendmaillogincoderequest-properties-locale.md "https://timelimit.io/SendMailLoginCodeRequest#/properties/locale") | +| [deviceAuthToken](#deviceauthtoken) | `string` | Optional | cannot be null | [SendMailLoginCodeRequest](sendmaillogincoderequest-properties-deviceauthtoken.md "https://timelimit.io/SendMailLoginCodeRequest#/properties/deviceAuthToken") | ## mail @@ -57,4 +58,22 @@ https://timelimit.io/SendMailLoginCodeRequest `string` +## deviceAuthToken + + + +`deviceAuthToken` + +* is optional + +* Type: `string` + +* cannot be null + +* defined in: [SendMailLoginCodeRequest](sendmaillogincoderequest-properties-deviceauthtoken.md "https://timelimit.io/SendMailLoginCodeRequest#/properties/deviceAuthToken") + +### deviceAuthToken Type + +`string` + # SendMailLoginCodeRequest Definitions diff --git a/other/mail/login/html.ejs b/other/mail/login/html.ejs index 5c93e3f..1624a87 100644 --- a/other/mail/login/html.ejs +++ b/other/mail/login/html.ejs @@ -2,8 +2,9 @@ - - + + + @@ -13,18 +14,6 @@ padding: 0; } - .ReadMsgBody { - width: 100%; - } - - .ExternalClass { - width: 100%; - } - - .ExternalClass * { - line-height: 100%; - } - body { margin: 0; padding: 0; @@ -53,29 +42,19 @@ margin: 13px 0; } - - - + - +
- -
+ +
-
- -
+
+ +
- - - + + + + +
-
TimeLimit
-
+
TimeLimit
+
- +
- -
+ +
-
- -
+
+ +
- - - + + + + +
-
-

- <%= introtext %> -

-

<%= code %>

-

- <%= outrotext %> -

-
-
+
+

<%= introtext %>

+

<%= code %>

+

<%= outrotext %>

<% if (deviceName !== null) { -%>

<%= deviceNameIntro %> <%= deviceName %> <%= deviceNameOutro %>

<% } -%> +
+
- +
- -
+ +
-
- -
+
+ +
- - - + + + + +
-

- -
+

+

+ +
- +
- -
+ +
-
- -
+
+ +
- - - + + + + +
-
-

You got this mail because your mail address was entered at the TimeLimit App for signing in. If you did not request this mail, then you can ignore it. If you have got any questions, then you can reply to this messagge.

-

- Sie erhalten diese Nachricht, da Ihre E-Mail-Adresse in der TimeLimit-App zum Anmelden eingegeben wurde. Wenn Sie diese E-Mail nicht angefordert haben, können Sie diese ignorieren. Falls Sie Fragen haben können Sie einfach auf - diese E-Mail antworten.

-

© - <%= mailimprint %> -

-
-
+
+

You got this mail because your mail address was entered at the TimeLimit App for signing in. If you did not request this mail, then you can ignore it. If you have got any questions, then you can reply to this messagge.

+

Sie erhalten diese Nachricht, da Ihre E-Mail-Adresse in der TimeLimit-App zum Anmelden eingegeben wurde. Wenn Sie diese E-Mail nicht angefordert haben, können Sie diese ignorieren. Falls Sie Fragen haben können Sie einfach auf diese E-Mail antworten.

+

© <%= mailimprint %>

+
+
- +
- +
diff --git a/other/mail/login/htmltemplate-src.txt b/other/mail/login/htmltemplate-src.txt index 03e2f33..f0395c6 100644 --- a/other/mail/login/htmltemplate-src.txt +++ b/other/mail/login/htmltemplate-src.txt @@ -13,6 +13,9 @@

<%= code %>

<%= outrotext %>

+ <% if (deviceName !== null) { -%> +

<%= deviceNameIntro %> <%= deviceName %> <%= deviceNameOutro %>

+ <% } -%> diff --git a/other/mail/login/text.ejs b/other/mail/login/text.ejs index 2776794..a0b1cc2 100644 --- a/other/mail/login/text.ejs +++ b/other/mail/login/text.ejs @@ -4,6 +4,10 @@ <%- outrotext %> +<% if (deviceName !== null) { -%> +<%- deviceNameIntro %> <%- deviceName %> <%- deviceNameOutro %> + +<% } -%> ---------------------- You got this mail because your mail address was entered at the TimeLimit App for signing in. diff --git a/src/api/auth.ts b/src/api/auth.ts index 985c598..ae13b7e 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -1,6 +1,6 @@ /* * server component for the TimeLimit App - * Copyright (C) 2019 - 2020 Jonas Lochmann + * Copyright (C) 2019 - 2021 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -48,6 +48,7 @@ export const createAuthRouter = (database: Database) => { } else { const { mailLoginToken } = await sendLoginCode({ mail, + deviceAuthToken: req.body.deviceAuthToken, locale: req.body.locale, database }) diff --git a/src/api/schema.ts b/src/api/schema.ts index 3f49ac5..7704cf3 100644 --- a/src/api/schema.ts +++ b/src/api/schema.ts @@ -132,6 +132,7 @@ export interface RequestWithAuthToken { export interface SendMailLoginCodeRequest { mail: string locale: string + deviceAuthToken?: string } export interface SignInByMailCodeRequest { diff --git a/src/api/validator.ts b/src/api/validator.ts index 6cfcd8a..b7ed729 100644 --- a/src/api/validator.ts +++ b/src/api/validator.ts @@ -2966,6 +2966,9 @@ export const isSendMailLoginCodeRequest: (value: object) => value is SendMailLog }, "locale": { "type": "string" + }, + "deviceAuthToken": { + "type": "string" } }, "additionalProperties": false, diff --git a/src/function/authentication/login-by-mail.ts b/src/function/authentication/login-by-mail.ts index 5247abb..1d65eac 100644 --- a/src/function/authentication/login-by-mail.ts +++ b/src/function/authentication/login-by-mail.ts @@ -1,6 +1,6 @@ /* * server component for the TimeLimit App - * Copyright (C) 2019 - 2020 Jonas Lochmann + * Copyright (C) 2019 - 2021 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -import { Forbidden, Gone, TooManyRequests } from 'http-errors' +import { Forbidden, Gone, TooManyRequests, Unauthorized } from 'http-errors' import { Database } from '../../database' import { sendAuthenticationMail } from '../../util/mail' import { areWordSequencesEqual, randomWords } from '../../util/random-words' @@ -23,12 +23,52 @@ import { checkMailSendLimit } from '../../util/ratelimit-authmail' import { generateAuthToken } from '../../util/token' import { createAuthTokenByMailAddress } from './index' -export const sendLoginCode = async ({ mail, locale, database }: { +export const sendLoginCode = async ({ mail, deviceAuthToken, locale, database }: { mail: string + deviceAuthToken?: string locale: string database: Database // no transaction here because this is directly called from an API endpoint }): Promise<{ mailLoginToken: string }> => { + let deviceName = null + + if (deviceAuthToken !== undefined) { + const info = await database.transaction(async (transaction) => { + const deviceEntryUnsafe = await database.device.findOne({ + where: { deviceAuthToken }, + attributes: ['familyId', 'name'], + transaction + }) + + if (!deviceEntryUnsafe) { + throw new Unauthorized() + } + + const deviceEntry = { + familyId: deviceEntryUnsafe.familyId, + name: deviceEntryUnsafe.name + } + + const userEntryCounter = await database.user.count({ + where: { + familyId: deviceEntry.familyId, + mail + }, + transaction + }) + + if (userEntryCounter === 1) { + return { deviceName: deviceEntry.name } + } else { + // do not show the device name if it is from another family + // otherwise third parties could chose a part of the content of the mail + return { deviceName: null } + } + }) + + deviceName = info.deviceName + } + try { await checkMailSendLimit(mail) } catch (ex) { @@ -41,7 +81,8 @@ export const sendLoginCode = async ({ mail, locale, database }: { await sendAuthenticationMail({ receiver: mail, code, - locale + locale, + deviceName }) await database.transaction(async (transaction) => { diff --git a/src/util/mail.ts b/src/util/mail.ts index 6aa66f2..2fb7bed 100644 --- a/src/util/mail.ts +++ b/src/util/mail.ts @@ -1,6 +1,6 @@ /* * server component for the TimeLimit App - * Copyright (C) 2019 - 2020 Jonas Lochmann + * Copyright (C) 2019 - 2021 Jonas Lochmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as @@ -36,7 +36,11 @@ const email = new Email({ } }) -export const sendAuthenticationMail = async ({ receiver, code, locale }: {receiver: string, code: string, locale: string}) => { +export const sendAuthenticationMail = async ({ + receiver, code, locale, deviceName +}: { + receiver: string, code: string, locale: string, deviceName: string | null +}) => { await email.send({ template: join(__dirname, '../../other/mail/login'), message: { @@ -47,7 +51,10 @@ export const sendAuthenticationMail = async ({ receiver, code, locale }: {receiv introtext: locale === 'de' ? 'Geben Sie zum Authentifizieren folgenden Code in TimeLimit ein' : 'To authenticate, enter the following code in TimeLimit', code, outrotext: locale === 'de' ? 'Geben Sie diesen Code nicht an Dritte weiter.' : 'Do not share this code with third parties.', - mailimprint + mailimprint, + deviceName, + deviceNameIntro: locale === 'de' ? 'Die Anmeldung wurde am Gerät' : 'The login was attempted at the device', + deviceNameOutro: locale === 'de' ? 'versucht.' : '.' } }) }