diff --git a/.env.example b/.env.example index d1fbeaa..c226705 100644 --- a/.env.example +++ b/.env.example @@ -33,8 +33,4 @@ GOOGLE_REDIRECT_URI=http://localhost:3030/oidc/callback/youtube TWITTER_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxx TWITTER_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -TWITTER_REDIRECT_URI=http://localhost:3030/oidc/callback/twitter - -SPOTIFY_CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxx -SPOTIFY_CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -SPOTIFY_REDIRECT_URI=http://localhost:3030/oidc/callback/spotify \ No newline at end of file +TWITTER_REDIRECT_URI=http://localhost:3030/oidc/callback/twitter \ No newline at end of file diff --git a/src/controllers/account/account.router.ts b/src/controllers/account/account.router.ts index 75e6a4b..9ebbf5e 100644 --- a/src/controllers/account/account.router.ts +++ b/src/controllers/account/account.router.ts @@ -13,9 +13,6 @@ import { getTwitterFollow } from './twitter/getFollow.action'; import { getYoutube } from './google/get.controller'; import { getYoutubeLike } from './google/youtube/like/get.controller'; import { getYoutubeSubscribe } from './google/youtube/subscribe/get.controller'; -import { getSpotifyUserFollow, getSpotifyPlaylistFollow } from './spotify/get.follow.action'; -import { getSpotifyTrackPlaying, getSpotifyTrackRecent, getSpotifyTrackSaved } from './spotify/get.track.action'; -import { getSpotify } from './spotify/get.action'; import { createLoginValidation, postLogin } from './login/post.controller'; const router = express.Router(); @@ -33,13 +30,6 @@ router.get('/:sub/google/youtube', guard.check(['account:read']), getYoutube); router.get('/:sub/google/youtube/like/:item', guard.check(['account:read']), getYoutubeLike); router.get('/:sub/google/youtube/subscribe/:item', guard.check(['account:read']), getYoutubeSubscribe); -router.get('/:sub/spotify', guard.check(['account:read']), getSpotify); -router.get('/:sub/spotify/user_follow/:item', guard.check(['account:read']), getSpotifyUserFollow); -router.get('/:sub/spotify/playlist_follow/:item', guard.check(['account:read']), getSpotifyPlaylistFollow); -router.get('/:sub/spotify/track_playing/:item', guard.check(['account:read']), getSpotifyTrackPlaying); -router.get('/:sub/spotify/track_recent/:item', guard.check(['account:read']), getSpotifyTrackRecent); -router.get('/:sub/spotify/track_saved/:item', guard.check(['account:read']), getSpotifyTrackSaved); - router.get('/address/:address', guard.check(['account:read']), validate([]), getAccountByAddress); router.get('/email/:email', guard.check(['account:read']), validate([]), getAccountByEmail); router.patch('/:id', guard.check(['account:read', 'account:write']), patchAccount); diff --git a/src/controllers/account/account.test.ts b/src/controllers/account/account.test.ts index 826829d..95156f3 100644 --- a/src/controllers/account/account.test.ts +++ b/src/controllers/account/account.test.ts @@ -5,7 +5,7 @@ import db from '../../util/database'; import { AccountService } from '../../services/AccountService'; import { INITIAL_ACCESS_TOKEN } from '../../util/secrets'; import { accountAddress, accountEmail, accountSecret } from '../../util/jest'; -import { SPOTIFY_API_ENDPOINT, TWITTER_API_ENDPOINT } from '../../util/secrets'; +import { TWITTER_API_ENDPOINT } from '../../util/secrets'; const http = request.agent(app); @@ -135,10 +135,6 @@ describe('Account Controller', () => { describe('GET /account/:sub/twitter', () => { beforeAll(async () => { - nock(SPOTIFY_API_ENDPOINT) - .persist() - .get(/.*?/) - .reply(200, { data: { items: [] } }); nock(TWITTER_API_ENDPOINT) .persist() .get(/.*?/) @@ -209,37 +205,4 @@ describe('Account Controller', () => { expect(res.body.isAuthorized).toEqual(true); }); }); - - describe('GET /account/:sub/spotify', () => { - it('Denice Access if there no authorization header', async () => { - const res = await http.get(`/account/${accountId}/spotify`).send(); - expect(res.status).toEqual(401); - }); - - it('Throw Error if there no linked spotify', async () => { - const res = await http - .get(`/account/${accountId}/spotify`) - .set({ - Authorization: authHeader, - }) - .send(); - expect(res.body.isAuthorized).toEqual(false); - }); - - it('Successfully get linked Spotify info with a correct infomation', async () => { - const account = await AccountService.getByEmail(accountEmail); - account.spotifyAccessToken = 'TOKEN'; - account.spotifyRefreshToken = 'REFRESH'; - account.spotifyAccessTokenExpires = (Date.now() + 1000000) * 1000; - await account.save(); - - const res = await http - .get(`/account/${accountId}/spotify`) - .set({ - Authorization: authHeader, - }) - .send(); - expect(res.body.isAuthorized).toEqual(true); - }); - }); }); diff --git a/src/controllers/account/get.action.ts b/src/controllers/account/get.action.ts index 413de36..dbb1976 100644 --- a/src/controllers/account/get.action.ts +++ b/src/controllers/account/get.action.ts @@ -18,7 +18,6 @@ function formatAccountRes(account: any) { email: account.email, googleAccess: account.googleAccessToken && account.googleAccessTokenExpires > Date.now(), twitterAccess: account.twitterAccessToken && account.twitterAccessTokenExpires > Date.now(), - spotifyAccess: account.spotifyAccessToken && account.spotifyAccessTokenExpires > Date.now(), }, ...protectedPrivateKey, }; diff --git a/src/controllers/account/patch.action.ts b/src/controllers/account/patch.action.ts index ae42b14..6edf464 100644 --- a/src/controllers/account/patch.action.ts +++ b/src/controllers/account/patch.action.ts @@ -11,7 +11,6 @@ export const patchAccount = async (req: Request, res: Response) => { address: req.body.address, googleAccess: req.body.googleAccess, twitterAccess: req.body.twitterAccess, - spotifyAccess: req.body.spotifyAccess, authenticationToken: req.body.authenticationToken, authenticationTokenExpires: req.body.authenticationTokenExpires, plan: req.body.plan, diff --git a/src/controllers/account/spotify/get.action.ts b/src/controllers/account/spotify/get.action.ts deleted file mode 100644 index e3e333d..0000000 --- a/src/controllers/account/spotify/get.action.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Request, Response } from 'express'; -import { AccountDocument } from '../../../models/Account'; -import { SpotifyService } from '../../../services/SpotifyService'; -import { AccountService } from '../../../services/AccountService'; - -export const getSpotify = async (req: Request, res: Response) => { - async function updateTokens(account: AccountDocument, newAccessToken: string) { - account.spotifyAccessToken = newAccessToken || account.spotifyAccessToken; - account.spotifyAccessTokenExpires = Date.now() + Number(3600) * 1000; - - return await account.save(); - } - - let account: AccountDocument = await AccountService.get(req.params.sub); - - if (!account.spotifyAccessToken || !account.spotifyRefreshToken) { - return res.json({ isAuthorized: false }); - } - - if (Date.now() >= account.spotifyAccessTokenExpires) { - const tokens = await SpotifyService.refreshTokens(account.spotifyRefreshToken); - account = await updateTokens(account, tokens); - } - - const user = await SpotifyService.getUser(account.spotifyAccessToken); - const playlists = await SpotifyService.getPlaylists(account.spotifyAccessToken); - const playlistItems = await Promise.all( - playlists.map((playlist) => SpotifyService.getPlaylistItems(account.spotifyAccessToken, playlist.id)), - ); - - res.json({ - isAuthorized: true, - playlists: playlists, - items: playlistItems.flat(), - users: [user], - }); -}; diff --git a/src/controllers/account/spotify/get.follow.action.ts b/src/controllers/account/spotify/get.follow.action.ts deleted file mode 100644 index 0ab78c1..0000000 --- a/src/controllers/account/spotify/get.follow.action.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Request, Response } from 'express'; -import { SpotifyService } from '../../../services/SpotifyService'; -import { AccountService } from '../../../services/AccountService'; - -export const getSpotifyUserFollow = async (req: Request, res: Response) => { - const account = await AccountService.get(req.params.sub); - if (!account) throw new Error('Account not found'); - - const result = await SpotifyService.validateUserFollow(account.spotifyAccessToken, [req.params.item]); - - res.json({ result }); -}; - -export const getSpotifyPlaylistFollow = async (req: Request, res: Response) => { - const account = await AccountService.get(req.params.sub); - if (!account) throw new Error('Account not found'); - - const user = await SpotifyService.getUser(account.spotifyAccessToken); - if (!user) throw new Error('User not found'); - - const result = await SpotifyService.validatePlaylistFollow(account.spotifyAccessToken, req.params.item, [user.id]); - - res.json({ result }); -}; diff --git a/src/controllers/account/spotify/get.track.action.ts b/src/controllers/account/spotify/get.track.action.ts deleted file mode 100644 index 7d42939..0000000 --- a/src/controllers/account/spotify/get.track.action.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Request, Response } from 'express'; -import { SpotifyService } from '../../../services/SpotifyService'; -import { AccountService } from '../../../services/AccountService'; - -export async function getSpotifyTrackPlaying(req: Request, res: Response) { - const account = await AccountService.get(req.params.sub); - const result = await SpotifyService.validateTrackPlaying(account.spotifyAccessToken, req.params.item); - - res.json({ result }); -} - -export async function getSpotifyTrackRecent(req: Request, res: Response) { - const account = await AccountService.get(req.params.sub); - const result = await SpotifyService.validateRecentTrack(account.spotifyAccessToken, req.params.item); - - res.json({ result }); -} - -export async function getSpotifyTrackSaved(req: Request, res: Response) { - const account = await AccountService.get(req.params.sub); - const result = await SpotifyService.validateSavedTracks(account.spotifyAccessToken, [req.params.item]); - - res.json({ result }); -} diff --git a/src/controllers/oidc/account/get.ts b/src/controllers/oidc/account/get.ts index d7b2151..c1538a0 100644 --- a/src/controllers/oidc/account/get.ts +++ b/src/controllers/oidc/account/get.ts @@ -1,5 +1,4 @@ import { Request, Response } from 'express'; -import { SpotifyService } from '../../../services/SpotifyService'; import { TwitterService } from '../../../services/TwitterService'; import { YouTubeService } from '../../../services/YouTubeService'; import { AccountService } from '../../../services/AccountService'; @@ -10,7 +9,6 @@ async function controller(req: Request, res: Response) { params.googleLoginUrl = YouTubeService.getLoginUrl(req.params.uid, YouTubeService.getBasicScopes()); params.twitterLoginUrl = TwitterService.getLoginURL(uid, {}); - params.spotifyLoginUrl = SpotifyService.getLoginURL(uid, {}); return res.render('account', { uid, @@ -27,7 +25,6 @@ async function controller(req: Request, res: Response) { otpSecret: account.otpSecret, googleAccess: account.googleAccessToken && account.googleAccessTokenExpires > Date.now(), twitterAccess: account.twitterAccessToken && account.twitterAccessTokenExpires > Date.now(), - spotifyAccess: account.spotifyAccessToken && account.spotifyAccessTokenExpires > Date.now(), }, }); } diff --git a/src/controllers/oidc/account/spotify/disconnect/post.controller.ts b/src/controllers/oidc/account/spotify/disconnect/post.controller.ts deleted file mode 100644 index 6a43abd..0000000 --- a/src/controllers/oidc/account/spotify/disconnect/post.controller.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Request, Response } from 'express'; -import { ERROR_NO_ACCOUNT } from '../../../../../util/messages'; -import { AccountService } from '../../../../../services/AccountService'; - -const controller = async (req: Request, res: Response) => { - const { uid, session } = req.interaction; - const account = await AccountService.get(session.accountId); - if (!account) throw new Error(ERROR_NO_ACCOUNT); - - await AccountService.update(account, { spotifyAccess: false }); - - res.redirect(`/oidc/${uid}/account`); -}; - -export default { controller }; diff --git a/src/controllers/oidc/callback/spotify/get.controller.ts b/src/controllers/oidc/callback/spotify/get.controller.ts deleted file mode 100644 index 9d92589..0000000 --- a/src/controllers/oidc/callback/spotify/get.controller.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Request, Response } from 'express'; -import { AccountDocument } from '../../../../models/Account'; -import { SpotifyService } from '../../../../services/SpotifyService'; -import { AccountService } from '../../../../services/AccountService'; -import { ERROR_NO_ACCOUNT } from '../../../../util/messages'; -import { getAccountByEmail, getInteraction, saveInteraction } from '../../../../util/oidc'; -import { AccountVariant } from '../../../../types/enums/AccountVariant'; - -async function updateTokens(account: AccountDocument, tokens: any): Promise { - account.spotifyAccessToken = tokens.access_token || account.spotifyAccessToken; - account.spotifyRefreshToken = tokens.refresh_token || account.spotifyRefreshToken; - account.spotifyAccessTokenExpires = - Date.now() + Number(tokens.expires_in) * 1000 || account.spotifyAccessTokenExpires; - - return await account.save(); -} - -async function getAccountBySub(sub: string): Promise { - const account = await AccountService.get(sub); - if (!account) throw new Error(ERROR_NO_ACCOUNT); - return account; -} - -async function controller(req: Request, res: Response) { - const code = req.query.code as string; - const uid = req.query.state as string; - const error = req.query.error as string; - if (error) return res.redirect(`/oidc/${uid}`); - - // Get all token information - const tokens = await SpotifyService.requestTokens(code); - const user = await SpotifyService.getUser(tokens.access_token); - const email = user.id + '@spotify.thx.network'; - - // Get the interaction based on the state - const interaction = await getInteraction(uid); - - // Check if there is an active session for this interaction - const account = - interaction.session && interaction.session.accountId - ? // If so, get account for sub - await getAccountBySub(interaction.session.accountId) - : // If not, get account for email claim - await getAccountByEmail(email, AccountVariant.SSOSpotify); - - // Actions after successfully login - await AccountService.update(account, { - lastLoginAt: Date.now(), - }); - - const returnTo = await saveInteraction(interaction, account._id.toString()); - - await updateTokens(account, tokens); - - return res.redirect(returnTo); -} - -export default { controller }; diff --git a/src/controllers/oidc/connect/get.ts b/src/controllers/oidc/connect/get.ts index 395acb9..3c37903 100644 --- a/src/controllers/oidc/connect/get.ts +++ b/src/controllers/oidc/connect/get.ts @@ -2,7 +2,6 @@ import { Request, Response } from 'express'; import { oidc } from '../../../util/oidc'; import { ChannelType } from '../../../models/Reward'; import { AccountService } from '../../../services/AccountService'; -import { SpotifyService } from '../../../services/SpotifyService'; import { TwitterService } from '../../../services/TwitterService'; import { YouTubeService } from '../../../services/YouTubeService'; @@ -23,8 +22,6 @@ async function controller(req: Request, res: Response) { } } else if (params.channel == ChannelType.Twitter && !account.twitterAccessToken) { redirect = TwitterService.getLoginURL(uid, {}); - } else if (params.channel == ChannelType.Spotify && !account.spotifyAccessToken) { - redirect = SpotifyService.getLoginURL(uid, {}); } if (!redirect) { diff --git a/src/controllers/oidc/oidc.router.ts b/src/controllers/oidc/oidc.router.ts index 57c32cc..3a7d66d 100644 --- a/src/controllers/oidc/oidc.router.ts +++ b/src/controllers/oidc/oidc.router.ts @@ -17,14 +17,12 @@ import CreateForgot from './forgot/post'; import CreateReset from './reset/post'; import ReadCallbackGoogle from './callback/google/get.controller'; import ReadCallbackTwitter from './callback/twitter/get.controller'; -import ReadCallbackSpotify from './callback/spotify/get.controller'; import ReadAccount from './account/get'; import UpdateAccount from './account/post'; import UpdateAccountTOTP from './account/totp/post'; import ReadAccountTOTP from './account/totp/get'; import PostGoogleDisconnect from './account/google/disconnect/post.controller'; import PostTwitterDisconnect from './account/twitter/disconnect/post.controller'; -import PostSpotifyDisconnect from './account/spotify/disconnect/post.controller'; import ReadAccountEmailVerify from './account/email/get'; import { assertInput, assertAuthorization, assertInteraction } from '../../middlewares'; @@ -32,7 +30,6 @@ const router = express.Router(); router.get('/callback/google', ReadCallbackGoogle.controller); router.get('/callback/twitter', ReadCallbackTwitter.controller); -router.get('/callback/spotify', ReadCallbackSpotify.controller); // Routes require no auth router.get('/:uid', assertInteraction, ReadOIDC.controller); @@ -57,12 +54,6 @@ router.get('/:uid/connect', assertInteraction, assertAuthorization, ReadConnect. router.get('/:uid/account', assertInteraction, assertAuthorization, ReadAccount.controller); router.post('/:uid/account/google/disconnect', assertInteraction, assertAuthorization, PostGoogleDisconnect.controller); -router.post( - '/:uid/account/spotify/disconnect', - assertInteraction, - assertAuthorization, - PostSpotifyDisconnect.controller, -); router.post( '/:uid/account/twitter/disconnect', assertInteraction, diff --git a/src/controllers/oidc/signin/get.ts b/src/controllers/oidc/signin/get.ts index 92494cf..cfe5057 100644 --- a/src/controllers/oidc/signin/get.ts +++ b/src/controllers/oidc/signin/get.ts @@ -1,6 +1,5 @@ import { Request, Response } from 'express'; import { AUTH_URL, WALLET_URL } from '../../../util/secrets'; -import { SpotifyService } from '../../../services/SpotifyService'; import { TwitterService } from '../../../services/TwitterService'; import { YouTubeService } from '../../../services/YouTubeService'; import { AUTH_REQUEST_TYPED_MESSAGE, createTypedMessage } from '../../../util/typedMessage'; @@ -11,7 +10,6 @@ async function controller(req: Request, res: Response) { if (params.return_url === WALLET_URL) { params.twitterLoginUrl = TwitterService.getLoginURL(uid, {}); - params.spotifyLoginUrl = SpotifyService.getLoginURL(uid, {}); params.authRequestMessage = createTypedMessage(AUTH_REQUEST_TYPED_MESSAGE, AUTH_URL, uid); } res.render('signin', { uid, params, alert: {} }); diff --git a/src/controllers/oidc/signin/signin.sso.test.ts b/src/controllers/oidc/signin/signin.sso.test.ts index 1e886c8..11dddb9 100644 --- a/src/controllers/oidc/signin/signin.sso.test.ts +++ b/src/controllers/oidc/signin/signin.sso.test.ts @@ -5,7 +5,7 @@ import app from '../../../app'; import { AccountService } from '../../../services/AccountService'; import db from '../../../util/database'; import { accountEmail, accountSecret } from '../../../util/jest'; -import { API_URL, INITIAL_ACCESS_TOKEN, SPOTIFY_API_ENDPOINT, TWITTER_API_ENDPOINT } from '../../../util/secrets'; +import { API_URL, INITIAL_ACCESS_TOKEN, TWITTER_API_ENDPOINT } from '../../../util/secrets'; const http = request.agent(app); @@ -128,30 +128,4 @@ describe('SSO Sign In', () => { expect(res.headers['location']).toContain('/auth/'); }); }); - - describe('Spotify SSO', () => { - beforeAll(async () => { - nock('https://accounts.spotify.com/api/token').post(/.*?/).reply(200, { - accessToken: 'thisnotgonnawork', - }); - nock(SPOTIFY_API_ENDPOINT + '/me') - .get(/.*?/) - .reply(200, { - data: { - id: 'thisnotgonnawork', - }, - }); - }); - - it('GET /oidc/callback/spotify', async () => { - const params = new URLSearchParams({ - code: 'thisnotgonnawork', - state: uid, - }); - const res = await http.get('/oidc/callback/spotify?' + params.toString()); - - expect(res.status).toBe(302); - expect(res.headers['location']).toContain('/auth/'); - }); - }); }); diff --git a/src/models/Account.ts b/src/models/Account.ts index cdf24cf..f2903e8 100644 --- a/src/models/Account.ts +++ b/src/models/Account.ts @@ -35,9 +35,6 @@ const accountSchema = new mongoose.Schema( twitterRefreshToken: String, twitterAccessTokenExpires: Number, twitterId: String, - spotifyAccessToken: String, - spotifyRefreshToken: String, - spotifyAccessTokenExpires: Number, verifyEmailToken: String, verifyEmailTokenExpires: Number, acceptTermsPrivacy: Boolean, diff --git a/src/models/Reward.ts b/src/models/Reward.ts index 5743b3b..53f1191 100644 --- a/src/models/Reward.ts +++ b/src/models/Reward.ts @@ -2,7 +2,6 @@ export enum ChannelType { None = 0, Google = 1, Twitter = 2, - Spotify = 3, } export enum ChannelAction { @@ -11,9 +10,4 @@ export enum ChannelAction { TwitterLike = 2, TwitterRetweet = 3, TwitterFollow = 4, - SpotifyUserFollow = 5, - SpotifyPlaylistFollow = 6, - SpotifyTrackPlaying = 7, - SpotifyTrackSaved = 8, - SpotifyTrackRecent = 9, } diff --git a/src/public/main.css b/src/public/main.css index a621e9d..771b9f3 100644 --- a/src/public/main.css +++ b/src/public/main.css @@ -9035,13 +9035,6 @@ textarea.form-control-underlined { background-color: #3367d6; } -.btn-spotify { - background-color: #1d75de; -} -.btn-spotify:active { - background-color: #1865c2; -} - .btn-metamask { background-color: #037dd6; } diff --git a/src/scss/_buttons.scss b/src/scss/_buttons.scss index 886142b..1dafc86 100644 --- a/src/scss/_buttons.scss +++ b/src/scss/_buttons.scss @@ -198,14 +198,6 @@ } } -.btn-spotify { - background-color: rgb(29, 117, 222); - - &:active { - background-color: rgb(24, 101, 194); - } -} - .btn-metamask { background-color: rgb(3, 125, 214); diff --git a/src/services/AccountService.ts b/src/services/AccountService.ts index 7b7f955..ca42ee8 100644 --- a/src/services/AccountService.ts +++ b/src/services/AccountService.ts @@ -53,7 +53,6 @@ export class AccountService { privateKey, googleAccess, twitterAccess, - spotifyAccess, authenticationToken, authenticationTokenExpires, lastLoginAt, @@ -130,13 +129,6 @@ export class AccountService { account.twitterRefreshToken = ''; account.twitterAccessTokenExpires = null; } - - if (spotifyAccess === false) { - account.spotifyAccessToken = ''; - account.spotifyRefreshToken = ''; - account.spotifyAccessTokenExpires = null; - } - return await account.save(); } diff --git a/src/services/SpotifyService.ts b/src/services/SpotifyService.ts deleted file mode 100644 index 381a997..0000000 --- a/src/services/SpotifyService.ts +++ /dev/null @@ -1,284 +0,0 @@ -import { spotifyClient } from '../util/axios'; -import { Playlist } from '../types'; -import { PlaylistItem } from '../types/PlaylistItem'; -import { SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, SPOTIFY_REDIRECT_URI } from '../util/secrets'; -import CommonOauthLoginOptions from 'types/CommonOauthLoginOptions'; - -export const SPOTIFY_API_SCOPE = [ - 'user-follow-read', - 'user-library-read', - 'user-read-recently-played', - 'user-read-currently-playing', - 'user-read-private', - 'user-read-email', -]; - -export const SPOTIFY_LIMITED_SCOPE = [ - 'user-follow-read', - 'user-library-read', - 'user-read-recently-played', - 'user-read-currently-playing', - 'user-read-private', -]; - -const ERROR_NO_DATA = 'Could not find an Spotify data for this accesstoken'; -const ERROR_NOT_AUTHORIZED = 'Not authorized for Spotify API'; -const ERROR_TOKEN_REQUEST_FAILED = 'Failed to request access token'; - -export class SpotifyService { - static async _fetchPlaylist(accessToken: string, offset = 0) { - const params = new URLSearchParams(); - params.set('offset', `${offset}`); - - const r = await spotifyClient({ - url: `/me/playlists?${params.toString()}`, - method: 'GET', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}`, - }, - }); - - if (r.status !== 200) throw new Error(ERROR_NOT_AUTHORIZED); - if (!r.data) throw new Error(ERROR_NO_DATA); - - return r.data; - } - - static async _fetchPlaylistItems(accessToken: string, playlistId: string, offset = 0) { - const params = new URLSearchParams(); - params.set('fields', 'items(track(id, name, album(images)))'); - params.set('offset', `${offset}`); - - const r = await spotifyClient({ - url: `/playlists/${playlistId}/tracks?${params.toString()}`, - method: 'GET', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}`, - }, - }); - - if (r.status !== 200) throw new Error(ERROR_NOT_AUTHORIZED); - if (!r.data) throw new Error(ERROR_NO_DATA); - - return r.data; - } - - static getLoginURL( - state: string, - { scope = SPOTIFY_API_SCOPE, redirectUrl = SPOTIFY_REDIRECT_URI }: CommonOauthLoginOptions, - ) { - const body = new URLSearchParams(); - - if (state) body.append('state', state); - body.append('response_type', 'code'); - body.append('client_id', SPOTIFY_CLIENT_ID); - body.append('redirect_uri', redirectUrl); - body.append('scope', scope.join(' ')); - - return `https://accounts.spotify.com/authorize?${body.toString()}`; - } - - static async requestTokens(code: string) { - const body = new URLSearchParams(); - body.append('code', code); - body.append('grant_type', 'authorization_code'); - body.append('redirect_uri', SPOTIFY_REDIRECT_URI); - - const r = await spotifyClient({ - url: 'https://accounts.spotify.com/api/token', - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Authorization': - 'Basic ' + Buffer.from(`${SPOTIFY_CLIENT_ID}:${SPOTIFY_CLIENT_SECRET}`).toString('base64'), - }, - data: body, - }); - - if (r.status !== 200) { - throw new Error('Failed to request access token'); - } - return r.data; - } - - static async getUser(accessToken: string) { - const r = await spotifyClient({ - url: '/me', - method: 'GET', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}`, - }, - }); - - if (r.status !== 200) throw new Error(ERROR_NOT_AUTHORIZED); - if (!r.data) throw new Error(ERROR_NO_DATA); - - return r.data; - } - - static async getPlaylists(accessToken: string) { - let playlists: Playlist[] = []; - let offset = 0; - - // eslint-disable-next-line no-constant-condition - while (true) { - const { error, ...data } = await this._fetchPlaylist(accessToken, offset); - if (error) throw new Error(error.message); - - const newPlaylists = (data.items as Playlist[]) || []; - - playlists = [...playlists, ...newPlaylists]; - if (!data.next) break; - - offset += 20; //20 is the max limit per request of spotify - } - - return playlists; - } - - static async getPlaylistItems(accessToken: string, playlistId: string) { - let items: PlaylistItem[] = []; - let offset = 0; - - // eslint-disable-next-line no-constant-condition - while (true) { - const { error, ...data } = await this._fetchPlaylistItems(accessToken, playlistId, offset); - if (error) throw new Error(error.message); - - const newItems = data.items as PlaylistItem[]; - - items = [...items, ...newItems]; - if (!data.next) break; - - offset += 100; //20 is the max limit per request of spotify - } - - return items; - } - - static async refreshTokens(refreshToken: string) { - const body = new URLSearchParams(); - body.append('grant_type', 'refresh_token'); - body.append('refresh_token', refreshToken); - - const r = await spotifyClient({ - url: 'https://accounts.spotify.com/api/token', - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Authorization': - 'Basic ' + Buffer.from(`${SPOTIFY_CLIENT_ID}:${SPOTIFY_CLIENT_SECRET}`).toString('base64'), - }, - data: body, - }); - - if (r.status !== 200) throw new Error(ERROR_TOKEN_REQUEST_FAILED); - - return r.data.access_token; - } - - /** CLAIM FLOW */ - static async validateUserFollow( - accessToken: string, - toIds: string[], - ): Promise> { - const params = new URLSearchParams(); - params.set('ids', `${toIds.join(',')}`); - params.set('type', 'user'); - - const r = await spotifyClient({ - url: `/me/following/contains?${params.toString()}`, - method: 'GET', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}`, - }, - }); - - if (r.status === 403) throw new Error(ERROR_NOT_AUTHORIZED); - if (!r.data) throw new Error(ERROR_NO_DATA); - - return (r.data as boolean[]).reduce((pre, cur, index) => ({ ...pre, [toIds[index]]: cur }), {}); - } - - static async validatePlaylistFollow(accessToken: string, playlistId: string, toIds: string[]) { - const params = new URLSearchParams(); - params.set('ids', `${toIds.join(',')}`); - - const r = await spotifyClient({ - url: `/playlists/${playlistId}/followers/contains?${params.toString()}`, - method: 'GET', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}`, - }, - }); - - if (r.status !== 200) throw new Error(ERROR_NOT_AUTHORIZED); - if (!r.data) throw new Error(ERROR_NO_DATA); - - return (r.data as boolean[]).reduce((pre, cur, index) => ({ ...pre, [toIds[index]]: cur }), {}); - } - - static async validateTrackPlaying(accessToken: string, trackId: string) { - const r = await spotifyClient({ - url: '/me/player/currently-playing', - method: 'GET', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}`, - }, - }); - - if (r.status === 403) throw new Error(ERROR_NOT_AUTHORIZED); - if (!r.data) return { result: false }; - - return { result: r.data.item.id === trackId }; - } - - static async validateRecentTrack(accessToken: string, trackId: string) { - const r = await spotifyClient({ - url: '/me/player/recently-played', - method: 'GET', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}`, - }, - }); - - if (r.status === 403) throw new Error(ERROR_NOT_AUTHORIZED); - if (!r.data) throw new Error(ERROR_NO_DATA); - - return { result: r.data.items.findIndex((item: any) => item.id === trackId) !== -1 }; - } - - static async validateSavedTracks(accessToken: string, toIds: string[]) { - const params = new URLSearchParams(); - params.set('ids', `${toIds.join(',')}`); - - const r = await spotifyClient({ - url: `/me/tracks/contains?${params.toString()}`, - method: 'GET', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}`, - }, - }); - - if (r.status === 403) throw new Error(ERROR_NOT_AUTHORIZED); - if (!r.data) throw new Error(ERROR_NO_DATA); - - return (r.data as boolean[]).reduce((pre, cur, index) => ({ ...pre, [toIds[index]]: cur }), {}); - } -} diff --git a/src/types/TAccount.ts b/src/types/TAccount.ts index 66e02c5..661f1f3 100644 --- a/src/types/TAccount.ts +++ b/src/types/TAccount.ts @@ -28,9 +28,6 @@ export interface TAccount { twitterRefreshToken: string; twitterAccessTokenExpires: number; twitterId?: string; - spotifyAccessToken: string; - spotifyRefreshToken: string; - spotifyAccessTokenExpires: number; verifyEmailToken: string; verifyEmailTokenExpires: number; lastLoginAt: number; @@ -46,7 +43,6 @@ export interface IAccountUpdates { privateKey?: string; googleAccess?: boolean; twitterAccess?: boolean; - spotifyAccess?: boolean; authenticationToken?: string; authenticationTokenExpires?: number; lastLoginAt?: number; diff --git a/src/util/axios.ts b/src/util/axios.ts index efef023..bffa64c 100644 --- a/src/util/axios.ts +++ b/src/util/axios.ts @@ -1,10 +1,5 @@ import axios, { AxiosRequestConfig } from 'axios'; -import { SPOTIFY_API_ENDPOINT, TWITTER_API_ENDPOINT } from './secrets'; - -export function spotifyClient(config: AxiosRequestConfig) { - config.baseURL = SPOTIFY_API_ENDPOINT; - return axios(config); -} +import { TWITTER_API_ENDPOINT } from './secrets'; export function twitterClient(config: AxiosRequestConfig) { config.baseURL = TWITTER_API_ENDPOINT; diff --git a/src/util/secrets.ts b/src/util/secrets.ts index 1a640d4..8a1a7ae 100644 --- a/src/util/secrets.ts +++ b/src/util/secrets.ts @@ -37,7 +37,6 @@ if (process.env.NODE_ENV === 'test') { export const VERSION = 'v1'; export const TWITTER_API_ENDPOINT = 'https://api.twitter.com/2'; -export const SPOTIFY_API_ENDPOINT = 'https://api.spotify.com/v1'; export const NODE_ENV = process.env.NODE_ENV; export const AUTH_URL = process.env.AUTH_URL; export const API_URL = process.env.API_URL; @@ -59,9 +58,6 @@ export const INITIAL_ACCESS_TOKEN = process.env.INITIAL_ACCESS_TOKEN; export const TWITTER_CLIENT_ID = process.env.TWITTER_CLIENT_ID; export const TWITTER_REDIRECT_URI = process.env.TWITTER_REDIRECT_URI; export const TWITTER_CLIENT_SECRET = process.env.TWITTER_CLIENT_SECRET; -export const SPOTIFY_CLIENT_ID = process.env.SPOTIFY_CLIENT_ID; -export const SPOTIFY_REDIRECT_URI = process.env.SPOTIFY_REDIRECT_URI; -export const SPOTIFY_CLIENT_SECRET = process.env.SPOTIFY_CLIENT_SECRET; export const AUTH_CLIENT_SECRET = process.env.AUTH_CLIENT_SECRET; export const AUTH_CLIENT_ID = process.env.AUTH_CLIENT_ID; export const JWKS_JSON = process.env.JWKS_JSON; diff --git a/src/util/social.ts b/src/util/social.ts index 20311a1..381a5e7 100644 --- a/src/util/social.ts +++ b/src/util/social.ts @@ -1,7 +1,6 @@ import { TwitterService } from '../services/TwitterService'; import { YouTubeService } from '../services/YouTubeService'; import { ChannelAction } from '../models/Reward'; -import { SPOTIFY_API_SCOPE, SpotifyService } from '../services/SpotifyService'; function getChannelScopes(channelAction: ChannelAction) { switch (channelAction) { @@ -12,12 +11,6 @@ function getChannelScopes(channelAction: ChannelAction) { case ChannelAction.TwitterRetweet: case ChannelAction.TwitterFollow: return { channelScopes: TwitterService.getScopes() }; - case ChannelAction.SpotifyPlaylistFollow: - case ChannelAction.SpotifyTrackPlaying: - case ChannelAction.SpotifyTrackRecent: - case ChannelAction.SpotifyTrackSaved: - case ChannelAction.SpotifyUserFollow: - return { channelScopes: SPOTIFY_API_SCOPE }; } } @@ -30,12 +23,6 @@ function getLoginLinkForChannelAction(uid: string, channelAction: ChannelAction) case ChannelAction.TwitterRetweet: case ChannelAction.TwitterFollow: return { twitterLoginUrl: TwitterService.getLoginURL(uid, {}) }; - case ChannelAction.SpotifyPlaylistFollow: - case ChannelAction.SpotifyTrackPlaying: - case ChannelAction.SpotifyTrackRecent: - case ChannelAction.SpotifyTrackSaved: - case ChannelAction.SpotifyUserFollow: - return { spotifyLoginUrl: SpotifyService.getLoginURL(uid, {}) }; } } diff --git a/src/views/account.ejs b/src/views/account.ejs index 532f53b..d28e15c 100644 --- a/src/views/account.ejs +++ b/src/views/account.ejs @@ -1,229 +1,182 @@
-
- THX Logo -
-
-
- - Account - - <% if (alert && alert.message) { %> - -
- <%= alert.message %> -
+
+ THX Logo +
+
+
+ + Account + + <% if (alert && alert.message) { %> + +
+ <%= alert.message %> +
+ <% } %> +
+ + <% if (params.profileImg) { %> +
+ profle-picture +
+ <% } %> +
+
+ Profile: +
+
+ +
+
+
+
+ Email: +
+
+ +
+
+ +
+
+ Address: +
+ +
+ +
+
+ +
+
+ First name: +
+
+ +
+
+ +
+
+ Last name: +
+
+ +
+
+ +
+
+ Organisation: +
+
+ +
+
+ + <% if (params.googleAccess) { %> + + <% } %> + + <% if (params.twitterAccess) { %> + + <% } %> + + + + +
+
+ Plan +
+
+ +
+
+ + +
+
+
+
+ MFA + Multi-factor Authentication using time-based one-time passwords + (TOTP). +
+
+ <% if (params.otpSecret) { %> +
+ + +
+ <% } %> + <% if (!params.otpSecret) { %> +
+ +
+ <% } %> +
+
+
+
+
+ Connect + Link your other accounts using single sign-on. +
+ +
+
+ <% if (params.googleLoginUrl && !params.googleAccess) { %> + + <% } %> + <% if (params.googleAccess) { %> +
+ +
<% } %> -
- - <% if (params.profileImg) { %> -
- profle-picture -
- <% } %> -
-
- Profile: -
-
- -
-
-
-
- Email: -
-
- -
-
- -
-
- Address: -
- -
- -
-
- -
-
- First name: -
-
- -
-
- -
-
- Last name: -
-
- -
-
- -
-
- Organisation: -
-
- -
-
- - <% if (params.googleAccess) { %> - - <% } %> - - <% if (params.twitterAccess) { %> - - <% } %> - - <% if (params.spotifyAccess) { %> - - <% } %> - - -
-
- Plan -
-
- -
-
- - -
-
-
-
- MFA - Multi-factor Authentication using time-based one-time passwords - (TOTP). -
-
- <% if (params.otpSecret) { %> -
- - -
- <% } %> - <% if (!params.otpSecret) { %> -
- -
- <% } %> -
-
-
-
-
- Connect - Link your other accounts using single sign-on. -
- -
-
- <% if (params.googleLoginUrl && !params.googleAccess) { %> - - <% } %> - <% if (params.googleAccess) { %> -
- -
- <% } %> - <% if (params.twitterLoginUrl && !params.twitterAccess) { %> - - <% } %> - <% if (params.twitterAccess) { %> -
- -
- <% } %> - <% if (params.spotifyLoginUrl && !params.spotifyAccess) - { %> - - <% } %> - <% if (params.spotifyAccess) { %> -
- -
- <% } %> -
-
-
- -
- - - Return to application - + <% if (params.twitterLoginUrl && !params.twitterAccess) { %> + + <% } %> + <% if (params.twitterAccess) { %> +
+ +
+ <% } %> +
+
+ +
+ + + Return to application +
+
\ No newline at end of file diff --git a/src/views/claim.ejs b/src/views/claim.ejs index f57f02c..346fbf5 100644 --- a/src/views/claim.ejs +++ b/src/views/claim.ejs @@ -44,22 +44,6 @@ follow this account. <% } %> - <% if (params.channelAction == 'SpotifyUserFollow' ) { %> - follow this account. - <% } %> - <% if (params.channelAction == 'SpotifyPlaylistFollow' ) { %> - follow this playlist. - <% } %> - <% if (params.channelAction == 'SpotifyTrackPlaying' ) { %> - play this track. - <% } %> - <% if (params.channelAction == 'SpotifyTrackSaved' ) { %> - save this track. - <% } %>

Make sure to grant these scopes: @@ -87,14 +71,6 @@

Sign in with Twitter
<% } %> - <% if (params.spotifyLoginUrl ) { %> - - <% } %>
diff --git a/src/views/signin.ejs b/src/views/signin.ejs index 8da9d18..7b2910d 100644 --- a/src/views/signin.ejs +++ b/src/views/signin.ejs @@ -89,48 +89,37 @@ <% } else { %> - + + <% if (params.googleLoginUrl) { %> <% } %> <% if (params.twitterLoginUrl) { %> - <% } %> - <% if (params.spotifyLoginUrl) { %> - <% } %> <% if (params.authRequestMessage) { %> <% } %>
- +