diff --git a/config/default.json b/config/default.json index 2608722..dd9fffe 100644 --- a/config/default.json +++ b/config/default.json @@ -33,8 +33,8 @@ "helmet": { "directives": { "defaultSrc": "'self'", - "childSrc": "'self' www.google.com", - "scriptSrc": "'self' 'unsafe-inline' 'unsafe-eval' www.google.com www.gstatic.com", + "childSrc": "'self' www.google.com telegram.org *.telegram.org", + "scriptSrc": "'self' 'unsafe-inline' 'unsafe-eval' www.google.com www.gstatic.com telegram.org *.telegram.org", "styleSrc": "'self' 'unsafe-inline' fonts.googleapis.com", "imgSrc": "* data:", "fontSrc": "data: fonts.gstatic.com", @@ -69,6 +69,10 @@ "enabled": true, "key": "9e9787bd59204b95812bc7e438939616", "secret": "ca10592646784980a11179cc72146344" + }, + "telegram": { + "enabled": true, + "bot_token": "6390290192:AAEGipeCa4P0V4HZzoXvITCij8on6OItsGM" } }, "default_client": "blogs", diff --git a/package.json b/package.json index 7103062..4ea011d 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "passport-facebook": "^3.0.0", "passport-mail": "^1.0.1", "passport-mailru-email": "^1.1.3", + "passport-telegram-official": "^2.0.1", "passport-vk": "^1.0.0", "passport-yandex": "^0.0.5", "querystring": "^0.2.1", @@ -37,6 +38,7 @@ "react-foundation-components": "git+https://github.com/golos-blockchain/react-foundation-components.git#5dbfb800aff45988c57bb7d09c1c235a8b49b418", "react-google-recaptcha": "^2.1.0", "react-intl": "^5.25.1", + "react-telegram-login": "^1.1.2", "secure-random": "^1.1.1", "simple-jsonrpc-js": "^1.2.0", "styled-components": "^5.3.3", diff --git a/src/elements/register/VerifyWayTabs.jsx b/src/elements/register/VerifyWayTabs.jsx index d7ce7c2..7823c0f 100644 --- a/src/elements/register/VerifyWayTabs.jsx +++ b/src/elements/register/VerifyWayTabs.jsx @@ -21,11 +21,11 @@ class VerifyWayTabs extends React.Component { const path = this.getPath() - let email = tt('verify_way_tabs_jsx.email') - if (currentWay === 'email') { - email = {email} + let social = tt('verify_way_tabs_jsx.social') + if (currentWay === 'social') { + social = {social} } else { - email = {email} + social = {social} } let invite = tt('verify_way_tabs_jsx.invite_code') @@ -43,7 +43,7 @@ class VerifyWayTabs extends React.Component { } return
- {email} + {social}  |  {invite}  |  diff --git a/src/locales/en.json b/src/locales/en.json index efbe28e..d899489 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -26,6 +26,7 @@ "enter_invite_code": "Enter invite code", "enter_invite_code_optional": "You also can use invite code, if you have it:", "or_use_socsite": "...or use social site", + "or_use_telegram": "...or use your Telegram account", "invite_secret_cannot_be_empty": "Invite code cannot be empty", "invite_new_account_will_receive": "Account will have %(amount)s in Golos Power", "enter_account_name": "Enter your account name", @@ -302,7 +303,7 @@ "frozen3": "this form." }, "verify_way_tabs_jsx": { - "email": "With Gmail", + "social": "Social", "invite_code": "Invite-code", "transfer": "Transfer" }, diff --git a/src/locales/ru-RU.json b/src/locales/ru-RU.json index 0f4a0be..0c4a261 100644 --- a/src/locales/ru-RU.json +++ b/src/locales/ru-RU.json @@ -26,6 +26,7 @@ "enter_invite_code": "Введите ваш инвайт-код", "enter_invite_code_optional": "Вы также можете использовать инвайт-код, если он у вас есть:", "or_use_socsite": "...или авторизуйтесь через свой аккаунт", + "or_use_telegram": "...или авторизуйтесь через свой аккаунт Telegram", "invite_secret_cannot_be_empty": "Инвайт-код не может быть пустым", "invite_new_account_will_receive": "Созданный аккаунт получит %(amount)s в Силу Голоса", "enter_account_name": "Введите имя пользователя", @@ -302,7 +303,7 @@ "frozen3": "этой формой." }, "verify_way_tabs_jsx": { - "email": "Gmail-почта", + "social": "Соцсети", "invite_code": "Инвайт-код", "transfer": "Перевод с биржи" }, diff --git a/src/pages/register/[[...client]].jsx b/src/pages/register/[[...client]].jsx index fe83b5a..ab7931a 100644 --- a/src/pages/register/[[...client]].jsx +++ b/src/pages/register/[[...client]].jsx @@ -5,6 +5,7 @@ import cn from 'classnames'; import { PrivateKey, } from 'golos-lib-js/lib/auth/ecc'; import Head from 'next/head'; import ReCAPTCHA from 'react-google-recaptcha'; +import TelegramLoginButton from 'react-telegram-login' import { SUPPORT_EMAIL } from '@/client_config'; import GeneratedPasswordInput from '@/elements/GeneratedPasswordInput'; @@ -44,19 +45,16 @@ class Register extends React.Component { state = { fetching: false, step: 'sending', - verificationWay: 'email', + verificationWay: 'social', message: '', name: '', - email: '', referrer: '', invite_code: '', code: '', password: '', passwordValid: '', nameError: '', - emailHint: '', - emailError: '', inviteHint: '', inviteError: '', recaptcha_v2: '', @@ -75,15 +73,18 @@ class Register extends React.Component { this.setState({referrer: invite}); } else { const verificationWay = 'invite_code' - if (this.state.invite_code !== invite || - this.state.verificationWay !== verificationWay) + if (this.state.verificationWay !== verificationWay) { this.setState({ - invite_code: invite, verificationWay, + }) + } + if (invite && this.state.invite_code !== invite) { + this.setState({ + invite_code: invite, }, () => { - if (invite) - this.validateInviteCode(invite); - }); + this.validateInviteCode(invite); + }) + } } } else if (params.has('transfer')) { const verificationWay = 'transfer' @@ -92,8 +93,8 @@ class Register extends React.Component { verificationWay, }) } else { - const verificationWay = 'email' - if (this.state.verificationWay !== verificationWay) + const verificationWay = 'social' + if (!this.state.verificationWay.startsWith(verificationWay)) this.setState({ verificationWay, }) @@ -170,10 +171,8 @@ class Register extends React.Component { message: (
{tt('register_jsx.authorizing_with') + socName + '...'} - {this._renderSocialButtons()} + {this._renderSocialButtons(false)}
), - - email: '', }); this.checkSocAuth(); @@ -183,18 +182,16 @@ class Register extends React.Component { this.updateApiState(result, () => { if (result.error) { this.setState({ - verificationWay: 'email' // to show social buttons again + verificationWay: 'social' // to show social buttons again }) } else { this.setState({ fetching: false, message: (
{tt('register_jsx.authorized_with_') + this.state.authType + '.'} - {this._renderSocialButtons()} + {this._renderSocialButtons(false)}
), verificationWay: 'social-' + socName, - - email: '', }) } }) @@ -230,6 +227,16 @@ class Register extends React.Component { window.open(`/api/reg/modal/mailru`, '_blank'); }; + useTelegram = (e) => { + e.preventDefault(); + const authType = 'Telegram' + this.setState({ + authType + }) + this.startSocialLoading(authType) + window.open(`/api/reg/modal/telegram`, '_blank'); + }; + useYandex = (e) => { e.preventDefault(); const authType = 'Яндекс' @@ -294,12 +301,9 @@ class Register extends React.Component { const { state, } = this; const { - email, name, passwordValid, nameError, - emailHint, - emailError, inviteHint, inviteError, submitting, @@ -312,17 +316,15 @@ class Register extends React.Component { return } - let emailConfirmStep = null; + let socialConfirmStep = null; let showMailForm = state.step === 'sending' && !state.verificationWay.startsWith('social-'); let isInviteWay = state.verificationWay === 'invite_code'; - if (state.step === 'sent' && state.verificationWay === 'email') { - emailConfirmStep = this._renderCodeWaiting(); - } else if (state.message) { - emailConfirmStep = ( + if (state.message) { + socialConfirmStep = (
- } else if (state.verificationWay === 'email') { + } else if (state.verificationWay === 'social') { const { dailyLimit } = this.props if (dailyLimit && dailyLimit.exceed) { form =
- +
{tt('register_jsx.email_exceed')}
@@ -395,54 +396,10 @@ class Register extends React.Component { {this._renderInviteCodeField(true)}
} - {!isInviteWay &&
- -
} - {this._renderSocialButtons()} -
- )} - {emailConfirmStep} - {showMailForm && !isInviteWay && ( -
- {state.fetching - && } -

- - {tt('g.continue')} - -

+ {this._renderSocialButtons(!(state.verificationWay === 'social'), !(state.verificationWay === 'social'))}
)} + {socialConfirmStep} {showMailForm && isInviteWay && (

@@ -533,53 +490,17 @@ class Register extends React.Component { ); } - _renderCodeWaiting() { - const { state, } = this; - - return ( -

-
- {tt('mobilevalidation_js.enter_confirm_code')} - -
-
{tt('mobilevalidation_js.waiting_from_you_line_2')}
- - -

- - {tt('mobilevalidation_js.you_can_change_your_number') + - ' '} - - {tt('mobilevalidation_js.select_another_number')} - . - -

- - -
-

{state.message}

-
- - - {tt('g.continue')} - -
- ); + onTelegramAuth = (user, x) => { + // dataAuthUrl='/api/reg/modal/telegram/callback' + console.log(user, x) + /*const response = await callApi('/api/reg/modal/telegram/callback'); + if (response.ok || response.status === 400) { + const result = await response.json(); + console.log(result) + }*/ } - _renderSocialButtons() { + _renderSocialButtons(showTitle = true, center = true) { const { config } = this.state; if (!config || !config.grants) { return null; @@ -592,11 +513,20 @@ class Register extends React.Component { const facebook = hasGrant('facebook'); const yandex = hasGrant('yandex'); const mailru = hasGrant('mailru'); - const empty = !vk && !facebook && !yandex && !mailru; + const telegram = hasGrant('telegram') + const empty = !vk && !facebook && !yandex && !mailru && !telegram + + if (!this.state.authType && !empty) { + const { dailyLimit } = this.props + if (dailyLimit && dailyLimit.exceed) { + return null + } + } return ( -
- {!this.state.authType && !empty && tt('register_jsx.or_use_socsite')}
+
+ {showTitle && !this.state.authType && !empty && tt('register_jsx.or_use_socsite')} + {showTitle ?
: null} {vk && VK @@ -617,6 +547,15 @@ class Register extends React.Component { Mail.Ru } + {telegram && !showTitle &&
+ {tt('register_jsx.or_use_telegram')} +
} + {telegram &&
+ +
}
); } @@ -682,7 +621,7 @@ class Register extends React.Component { _onSubmit = async e => { e.preventDefault(); this.setState({ submitting: true }); - const { email, invite_code, name, password, passwordValid, referrer, recaptcha_v2, } = this.state; + const { verificationWay, invite_code, name, password, passwordValid, referrer, recaptcha_v2, } = this.state; if (!name || !password || !passwordValid) return; let publicKeys; @@ -707,8 +646,7 @@ class Register extends React.Component { try { // create account const res = await callApi('/api/reg/submit', { - email: email !== '' ? email : undefined, - invite_code: email === '' ? invite_code : undefined, + invite_code: verificationWay === 'invite_code' ? invite_code : undefined, name, owner_key: publicKeys[0], active_key: publicKeys[1], @@ -755,30 +693,6 @@ class Register extends React.Component { this.setState({ code }); }; - validateEmail = (value, isFinal) => { - const { config, } = this.state; - - const fakeEmailsAllowed = config && config.fake_emails_allowed; - - let emailError = null; - let emailHint = null; - - if (!value) { - emailError = tt('mobilevalidation_js.email_cannot_be_empty'); - } else if (!fakeEmailsAllowed && !/^[a-z0-9](\.?[a-z0-9]){5,}@g(oogle)?mail\.com$/.test(value)) { - emailError = tt('mobilevalidation_js.email_must_be_gmail'); - } - - if (emailError) { - emailError = - '' + emailError; - } else { - emailHint = 'Google email: ' + value; - } - - this.setState({ emailError, emailHint }); - }; - validateInviteCode = async (value, isFinal) => { let inviteError = null; let inviteHint = null; @@ -830,7 +744,7 @@ class Register extends React.Component { if (error_str) { newState.message = error_str; } - if (verification_way === 'email' && step === 'verified') { + if (verification_way === 'social' && step === 'verified') { newState.message = tt( 'register_jsx.phone_number_has_been_verified' ); @@ -839,44 +753,10 @@ class Register extends React.Component { this.setState(newState, after); } - onClickSelectAnotherPhone = () => { - this.setState({ - fetching: false, - step: 'sending', - }); - }; - - onClickSendCode = async () => { - const { email } = this.state; - - this.setState({ - fetching: true, - }); - - try { - const res = await callApi('/api/reg/send_code', { - email - }); - - let data = await res.json(); - - this.updateApiState(data); - } catch (err) { - console.error('Caught /send_code server error', err); - - this.updateApiState({ - status: 'err', - error_str: err.message ? err.message : err, - }); - } - }; - onClickContinueInvite = async () => { this.setState({ fetching: true, message: '', - - email: '', }); const res = await callApi('/api/reg/use_invite', { @@ -888,35 +768,6 @@ class Register extends React.Component { this.updateApiState(data); }; - onCheckCode = async () => { - try { - const res = await callApi('/api/reg/verify_code', { - confirmation_code: this.state.code, - email: this.state.email - }); - - let data = await res.json(); - - this.updateApiState(data); - } catch (err) { - console.error('Caught /verify_code server error:', err); - this.updateApiState({ - status: 'err', - error_str: err.message ? err.message : err, - }); - } - }; - - onEmailChange = e => { - // продолжаем let - let email = e.target.value.trim().toLowerCase() - this.validateEmail(email) - - this.setState({ - email - }); - }; - onInviteCodeChange = e => { // продолжаем let let invite_code = e.target.value.trim() diff --git a/src/server/passport.js b/src/server/passport.js index 4565f11..a03c381 100644 --- a/src/server/passport.js +++ b/src/server/passport.js @@ -5,6 +5,7 @@ import { Strategy as VKontakteStrategy, } from 'passport-vk'; import { Strategy as FacebookStrategy, } from 'passport-facebook'; import { Strategy as MailruStrategy, } from 'passport-mail'; import { Strategy as YandexStrategy, } from 'passport-yandex'; +import { TelegramStrategy } from 'passport-telegram-official' import { throwErr, } from '@/server/error' import Tarantool from '@/server/tarantool'; import { getRemoteIp, } from '@/server/misc'; @@ -32,56 +33,76 @@ export const checkAlreadyUsed = async (req, verifyType, verifyId, errBodyProps = const strategies = { vk: VKontakteStrategy, facebook: FacebookStrategy, - mailru: MailruStrategy, yandex: YandexStrategy + mailru: MailruStrategy, yandex: YandexStrategy, + telegram: TelegramStrategy }; for (const [grantId, grant] of Object.entries(config.grant)) { const strategy = strategies[grantId]; if (!strategy || !grant.enabled) continue; try { - passport.use(new strategy( - { - clientID: grant.key, - clientSecret: grant.secret, - callbackURL: `${config.rest_api}/api/reg/modal/${grantId}/callback`, - passReqToCallback: true - }, - async (req, accessToken, refreshToken, params, profile, done) => { - try { - delete req.session.soc_error - req.session.soc_id = profile.id; - req.session.soc_id_type = grantId + '_id'; + let opts + const verifyCallback = async (req, profile, done) => { + console.log('TS callback') + console.log(profile) + try { + delete req.session.soc_error + req.session.soc_id = profile.id; + req.session.soc_id_type = grantId + '_id'; - const idHash = hash.sha256(req.session.soc_id.toString(), 'hex'); + const idHash = hash.sha256(req.session.soc_id.toString(), 'hex'); - console.log('-- social existing id'); + console.log('-- social existing id'); - await checkAlreadyUsed(req, 'social-' + grantId, idHash) + await checkAlreadyUsed(req, 'social-' + grantId, idHash) - console.log('-- social select user'); + console.log('-- social select user'); - let user = await Tarantool.instance('tarantool').select('users', 'by_verify_uid', - 1, 0, 'eq', ['social-' + grantId, idHash, req.session.uid]); + let user = await Tarantool.instance('tarantool').select('users', 'by_verify_uid', + 1, 0, 'eq', ['social-' + grantId, idHash, req.session.uid]); - if (!user[0]) { - console.log('-- social insert user'); - user = await Tarantool.instance('tarantool').insert('users', - [null, req.session.uid, 'social-' + grantId, idHash, true, '1234', getRemoteIp(req), false]); - } + if (!user[0]) { + console.log('-- social insert user'); + user = await Tarantool.instance('tarantool').insert('users', + [null, req.session.uid, 'social-' + grantId, idHash, true, '1234', getRemoteIp(req), false]); + } - req.session.user = user[0][0]; + req.session.user = user[0][0]; - await req.session.save(); - } catch (err) { - console.error('social error:', err) + await req.session.save(); + } catch (err) { + console.error('social error:', err) - req.session.soc_error = err - delete req.session.soc_id - req.session.soc_id_type = grantId + '_id' + req.session.soc_error = err + delete req.session.soc_id + req.session.soc_id_type = grantId + '_id' - await req.session.save() - } - done(null, {profile}); + await req.session.save() + } + done(null, {profile}); + } + + let callbackWrapper + if (grantId === 'telegram') { + opts = { + botToken: grant.bot_token, + passReqToCallback: true, } + callbackWrapper = verifyCallback + } else { + opts = { + clientID: grant.key, + clientSecret: grant.secret, + callbackURL: `${config.rest_api}/api/reg/modal/${grantId}/callback`, + } + callbackWrapper = async (req, accessToken, refreshToken, params, profile, done) => { + return await verifyCallback(req, profile, done) + } + } + passport.use(new strategy( + { + ...opts, + }, + callbackWrapper )); } catch (ex) { console.error(`ERROR: Wrong config.grant.${grantId} settings. Fix them or just disable registration with ${grantId}. Error is following:`) @@ -123,6 +144,12 @@ export const addModalRoutes = (handler) => { failureRedirect: '/api/reg/modal/failure' })) + .get('/api/reg/modal/telegram', authenticator('telegram')) + .get('/api/reg/modal/telegram/callback', passport.authenticate('telegram', { + successRedirect: '/api/reg/modal/success', + failureRedirect: '/api/reg/modal/failure' + })) + .get('/api/reg/modal/success', (req, res) => { res.status(200) .setHeader('Content-Type', 'text/html; charset=utf-8') diff --git a/yarn.lock b/yarn.lock index 344eaad..92846be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4401,11 +4401,18 @@ passport-oauth@1.0.x: passport-oauth1 "1.x.x" passport-oauth2 "1.x.x" -passport-strategy@1.x.x: +passport-strategy@1.x.x, passport-strategy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" integrity sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ= +passport-telegram-official@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/passport-telegram-official/-/passport-telegram-official-2.0.1.tgz#c54bd5c82aab904c2e4466a280806c19da94366e" + integrity sha512-h7ugNskZjkouF4HuOeYs25bmAg/Ct7jcOTaB1lZgWPXAX8SL9NRp11+h1b0udpKodYRgnFfR5tS6IummI0qVLQ== + dependencies: + passport-strategy "^1.0.0" + passport-vk@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-vk/-/passport-vk-1.0.0.tgz#acdb98a06149d7ff2bddf7b6f0c8fa54319ff32f" @@ -4616,6 +4623,15 @@ prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" +prop-types@^15.6.2: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + psl@^1.1.28: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -4801,7 +4817,7 @@ react-is@17.0.2, react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.1: +react-is@^16.13.1, react-is@^16.3.2, react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -4822,6 +4838,22 @@ react-refresh@0.8.3: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg== +react-telegram-login@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/react-telegram-login/-/react-telegram-login-1.1.2.tgz#28b9bdd68bb2710afca19354ac1f9428092836f0" + integrity sha512-pDP+bvfaklWgnK5O6yvZnIwgky0nnYUU6Zhk0EjdMSkPsLQoOzZRsXIoZnbxyBXhi7346bsxMH+EwwJPTxClDw== + dependencies: + react "^16.13.1" + +react@^16.13.1: + version "16.14.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" + integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"