@schibsted/account-sdk-browser
Advanced tools
Comparing version 3.4.0 to 4.0.0
# Changelog | ||
## v4.0.0 (2020-09-03) | ||
## Breaking changes | ||
* `sessionDomain` is required | ||
* Removed `siteSpecificLogout` param- there is no option to disable site specific logout | ||
* `hasSession` does not accept `autologin` param anymore | ||
* Drop `hasProduct` and `hasSubscription` functions- replaced by `hasAccess` | ||
## New features | ||
* Add 2FA Support- new `acrValues`: `sms`, `otp`, `password` | ||
* `logSettings` function | ||
## Changes | ||
* babel has been updated to `^7.11` | ||
## v3.4.0 (2020-04-28) | ||
@@ -4,0 +19,0 @@ |
@@ -9,4 +9,5 @@ export class Identity { | ||
hasSession(): Promise<any> | ||
logSettings(): void | ||
_enableSessionCaching: boolean; | ||
_itpMode: boolean; | ||
} |
{ | ||
"name": "@schibsted/account-sdk-browser", | ||
"version": "3.4.0", | ||
"version": "4.0.0", | ||
"description": "Schibsted account SDK for browsers", | ||
@@ -20,20 +20,20 @@ "main": "index.js", | ||
"dependencies": { | ||
"fetch-jsonp": "^1.1.3", | ||
"tiny-emitter": "^2.1.0" | ||
}, | ||
"devDependencies": { | ||
"babel-core": "^6.26.3", | ||
"babel-loader": "^7.1.4", | ||
"babel-preset-env": "^1.7.0", | ||
"@babel/core": "^7.11.4", | ||
"@babel/preset-env": "^7.11.0", | ||
"babel-loader": "^8.1.0", | ||
"codecov": "^3.6.5", | ||
"core-js": "^3.6.5", | ||
"docdash": "git+https://github.com/torarvid/docdash.git#v0.5.0", | ||
"eslint": "^6.8.0", | ||
"eslint-plugin-import": "^2.20.2", | ||
"jest": "^23.6.0", | ||
"jest": "^26.4.2", | ||
"jest-junit": "^10.0.0", | ||
"jsdoc": "^3.6.4", | ||
"jsdoc": "^3.6.5", | ||
"node-fetch": "^2.6.0", | ||
"regenerator-runtime": "^0.13.5", | ||
"webpack": "^4.43.0", | ||
"webpack-cli": "^3.3.11", | ||
"regenerator-runtime": "^0.13.7", | ||
"webpack": "^4.44.1", | ||
"webpack-cli": "^3.3.12", | ||
"whatwg-url": "^8.0.0" | ||
@@ -48,4 +48,6 @@ }, | ||
[ | ||
"env", | ||
"@babel/preset-env", | ||
{ | ||
"useBuiltIns": "usage", | ||
"corejs": 3, | ||
"targets": { | ||
@@ -52,0 +54,0 @@ "browsers": [ |
@@ -153,17 +153,4 @@ [](https://github.com/schibsted/account-sdk-browser) | ||
There are two ways to deal with this in Safari: | ||
1. You can continue with 3rd party requests, but this requires an iframe on that 3rd party domain | ||
and it also requires user input in said iframe | ||
2. You can re-design the system to not use 3rd party requests anymore | ||
We've tried to support both. For the 1st strategy, simply continue using the SDK like before. Be | ||
sure to call `Identity.login` when authenticating, and `Identity.hasSession` when coming back to | ||
your site from Schibsted account. This should pop up our so-called "ITP Dialog". This is the iframe | ||
mentioned in point 1 above, and clicking the Continue button in that frame will ensure the | ||
`hasSession` call running inside the iframe is successful. The benefit of this strategy is it | ||
requires very little work from you. The drawback is that every time you come back from | ||
authentication, the user will have to see this "ITP dialog". | ||
So to work with strategy 2, we have re-designed our platform and introduced what we call the | ||
To ensure consistent user sessions despite these restrictions, we have re-designed our platform and | ||
introduced what we call the | ||
session-service. If your site lives on site.example, you should assign a sub-domain for use with the | ||
@@ -189,5 +176,3 @@ session-service (we propose id.site.example for production and id-pre.site.example for staging — | ||
1 and 2 requires communication with us, and 3 is done by you at a time of your choosing. The benefit | ||
of this strategy is that we should never need to show any dialog or popup to the user, so it reduces | ||
friction. The drawback is the work mentioned above. | ||
1 and 2 requires [communication with us](mailto:schibstedaccount@schibsted.com), and 3 is done by you at a time of your choosing. | ||
@@ -312,2 +297,4 @@ <a name="example-project"></a> | ||
the code on their phone as an SMS | ||
* Multifactor authentication: first client indicates which methods should be preferred, later these | ||
will be included (if fulfilled) in `AMR` claim of IDToken | ||
@@ -318,4 +305,10 @@ IMPORTANT: Passwordless using SMS is still in BETA. It's only recommended to use it for testing and | ||
The default is username & password. If you wish to use one of the passwordless login methods, the | ||
`login()` function takes an optional parameter called `acrValues` (yeah, it's an OAuth specific | ||
name). Please set this parameter to either `otp-email` or `otp-sms`. | ||
`login()` function takes an optional parameter called `acrValues` (Authentication Context Class Reference). | ||
The `acrValues` parameter with multifactor authentication can take following values: | ||
- `otp-email` - passwordless authentication using code sent to registered email | ||
- `otp-sms` - passwordless authentication using code sent to registered phone number | ||
- `password` - force password authentication (even if user is already logged in) | ||
- `otp` - authentication using registered one time code generator (https://tools.ietf.org/html/rfc6238) | ||
- `sms` - authentication using SMS code sent to phone number | ||
- `password otp sms` - those authentication methods might be combined | ||
@@ -397,35 +390,2 @@ The classic way to authenticate a user, is to send them from your site to the Schibsted account | ||
### Legacy methods | ||
* [Monetization#hasProduct](https://schibsted.github.io/account-sdk-browser/Monetization.html#hasProduct) | ||
for checking if the user has access to a particular product | ||
* [Monetization#hasSubscription](https://schibsted.github.io/account-sdk-browser/Monetization.html#hasSubscription) | ||
for checking if the user has access to a particular subscription | ||
These two functions require a parameter `sp_id` that is obtained from | ||
[Identity#getSpId](https://schibsted.github.io/account-sdk-browser/Identity.html#getSpId) | ||
asynchronously. | ||
#### Example | ||
```javascript | ||
import { Monetization } from '@schibsted/account-sdk-browser' | ||
const monetization = new Monetization({ | ||
clientId: '56e9a5d1eee0000000000000', | ||
redirectUri: 'https://awesomenews.site', // ensure it's listed in selfservice | ||
env: 'PRE', // Schibsted account env. A url or a special key: 'PRE', 'PRO' or 'PRO_NO' | ||
}) | ||
try { | ||
// Check if the user has access to a a particular product | ||
// You need the sp_id parameter that is obtained from an Identity instance | ||
const sp_id = await identity.getSpId() | ||
const data = await monetization.hasProduct(productId, sp_id) | ||
alert(`User has access to ${productId}? ${data.result}`) | ||
} catch (err) { | ||
alert(`Could not query if the user has access to ${productId} because ${err}`) | ||
} | ||
``` | ||
<a name="payment"></a> | ||
@@ -432,0 +392,0 @@ |
@@ -1,27 +0,7 @@ | ||
import { URLSearchParams, URL } from 'url'; | ||
import { URL } from 'url'; | ||
import { urlMapper } from '../url'; | ||
import { cloneDefined } from '../object'; | ||
import { Fixtures } from '../../__tests__/utils'; | ||
import SDKError from '../../src/SDKError'; | ||
const goFn = () => jest.fn().mockImplementation(async ({ pathname, data = {} }) => { | ||
const search = new URLSearchParams(data); | ||
if (pathname.startsWith('/hasProduct/')) { | ||
if (pathname.endsWith('/existing')) { | ||
return Fixtures.spidProduct; | ||
} else if (pathname.endsWith('no-session-cookie')) { | ||
throw new SDKError('Session cookie (schacc-session) missing', { code: 400 }); | ||
} else if (pathname.endsWith('no-session')) { | ||
throw new SDKError('No session', { code: 401 }); | ||
} | ||
} | ||
if (pathname.startsWith('/hasSubscription/')) { | ||
if (pathname.endsWith('/existing')) { | ||
return Fixtures.spidProduct; | ||
} else if (pathname.endsWith('no-session-cookie')) { | ||
throw new SDKError('Session cookie (schacc-session) missing', { code: 400 }); | ||
} else if (pathname.endsWith('no-session')) { | ||
throw new SDKError('No session', { code: 401 }); | ||
} | ||
} | ||
const goFn = () => jest.fn().mockImplementation(async ({ pathname }) => { | ||
if (pathname.startsWith('/hasAccess/')) { | ||
@@ -36,32 +16,2 @@ if (pathname.endsWith('/existing')) { | ||
} | ||
if (pathname === 'ajax/hasproduct.js') { | ||
const productId = search.get('product_id'); | ||
const spId = search.get('sp_id'); | ||
switch (productId) { | ||
case 'existing': | ||
return Fixtures.spidProduct; | ||
case 'existing_no_expires': | ||
return Fixtures.spidProductNoExpires; | ||
case 'existing_for_john': | ||
if (spId === 'john') { | ||
return Fixtures.spidProduct; | ||
} | ||
} | ||
return Fixtures.spidProductMissing; | ||
} | ||
if (pathname === 'ajax/hassubscription.js') { | ||
const subscriptionId = search.get('product_id'); | ||
const spId = search.get('sp_id'); | ||
switch (subscriptionId) { | ||
case 'existing': | ||
return Fixtures.spidProduct; | ||
case 'existing_no_expires': | ||
return Fixtures.spidProductNoExpires; | ||
case 'existing_for_john': | ||
if (spId === 'john') { | ||
return Fixtures.spidProduct; | ||
} | ||
} | ||
return Fixtures.spidProductMissing; | ||
} | ||
throw new Error(`Unimplemented mock response for url: '${pathname}'`); | ||
@@ -68,0 +18,0 @@ }); |
@@ -31,8 +31,2 @@ /* Copyright 2018 Schibsted Products & Technology AS. Licensed under the terms of the MIT license. | ||
* @prop {string} ENDPOINTS.SPiD.PRO_NO - Production environment Norway | ||
* @prop {object} ENDPOINTS.HAS_SESSION - Endpoints to check whether a user has a valid session | ||
* @prop {string} ENDPOINTS.HAS_SESSION.LOCAL - Local endpoint (for Identity team) | ||
* @prop {string} ENDPOINTS.HAS_SESSION.DEV - Dev environment (for Identity team) | ||
* @prop {string} ENDPOINTS.HAS_SESSION.PRE - Staging environment | ||
* @prop {string} ENDPOINTS.HAS_SESSION.PRO - Production environment Sweden | ||
* @prop {string} ENDPOINTS.HAS_SESSION.PRO_NO - Production environment Norway | ||
* @prop {object} ENDPOINTS.BFF - Endpoints used with new GDPR-compliant web flows | ||
@@ -50,4 +44,2 @@ * @prop {string} ENDPOINTS.BFF.LOCAL - Local endpoint (for Identity team) | ||
* @prop {string} ENDPOINTS.SESSION_SERVICE.PRO_NO - Production environment Norway | ||
* @prop {object} JSONP | ||
* @prop {Number} JSONP.TIMEOUT=7000 - Timeout in milliseconds | ||
*/ | ||
@@ -63,9 +55,2 @@ const config = { | ||
}, | ||
HAS_SESSION: { | ||
LOCAL: 'http://session.id.localhost', | ||
DEV: 'https://session.identity-dev.schibsted.com', | ||
PRE: 'https://session.identity-pre.schibsted.com', | ||
PRO: 'https://session.login.schibsted.com', | ||
PRO_NO: 'https://session.payment.schibsted.no' | ||
}, | ||
BFF: { | ||
@@ -86,3 +71,2 @@ LOCAL: 'http://id.localhost/authn/', | ||
}, | ||
JSONP: { TIMEOUT: 7000 }, // ms | ||
NAMESPACE: { | ||
@@ -95,7 +79,6 @@ LOCAL: 'id.localhost', | ||
} | ||
} | ||
}; | ||
export default config; | ||
export const ENDPOINTS = config.ENDPOINTS; | ||
export const JSONP = config.JSONP; | ||
export const NAMESPACE = config.NAMESPACE; |
@@ -12,9 +12,8 @@ /* Copyright 2018 Schibsted Products & Technology AS. Licensed under the terms of the MIT license. | ||
import EventEmitter from 'tiny-emitter'; | ||
import JSONPClient from './JSONPClient'; | ||
import Cache from './cache'; | ||
import * as popup from './popup'; | ||
import ItpModal from './ItpModal'; | ||
import RESTClient from './RESTClient'; | ||
import SDKError from './SDKError'; | ||
import * as spidTalk from './spidTalk'; | ||
import { version } from '../package.json'; | ||
@@ -74,19 +73,5 @@ /** | ||
const HAS_SESSION_CACHE_KEY = 'hasSession-cache'; | ||
const LOGIN_IN_PROGRESS_KEY = 'loginInProgress-cache'; | ||
const globalWindow = () => window; | ||
/** | ||
* Get type and value of something | ||
* @private | ||
* @param {string} thing | ||
* @returns {Array} Tuple of [type, value] | ||
*/ | ||
function inspect(thing) { | ||
if (thing === null) { | ||
return [typeof thing, `${thing}`]; | ||
} | ||
return [thing.constructor.name, thing.valueOf()]; | ||
} | ||
/** | ||
* Provides Identity functionalty to a web page | ||
@@ -98,6 +83,5 @@ */ | ||
* @param {string} options.clientId - Example: "1234567890abcdef12345678" | ||
* @param {string} options.sessionDomain - Example: "https://id.site.com" | ||
* @param {string} [options.redirectUri] - Example: "https://site.com" | ||
* @param {string} [options.sessionDomain] - Example: "https://id.site.com" | ||
* @param {string} [options.env='PRE'] - Schibsted account environment: `PRE`, `PRO` or `PRO_NO` | ||
* @param {boolean} [options.siteSpecificLogout=true] - Whether site-specific logout should be used | ||
* @param {function} [options.log] - A function that receives debug log information. If not set, | ||
@@ -107,3 +91,3 @@ * no logging will be done | ||
*/ | ||
constructor({ clientId, redirectUri, sessionDomain, env = 'PRE', siteSpecificLogout = true, log, window = globalWindow() }) { | ||
constructor({ clientId, redirectUri, sessionDomain, env = 'PRE', log, window = globalWindow() }) { | ||
super(); | ||
@@ -113,2 +97,3 @@ assert(isNonEmptyString(clientId), 'clientId parameter is required'); | ||
assert(!redirectUri || isUrl(redirectUri), 'redirectUri parameter is invalid'); | ||
assert(sessionDomain && isUrl(sessionDomain), 'sessionDomain parameter is not a valid URL'); | ||
@@ -122,24 +107,15 @@ spidTalk.emulate(window); | ||
this.env = env; | ||
this.siteSpecificLogout = siteSpecificLogout; | ||
this.log = log; | ||
this._sessionDomain = sessionDomain; | ||
if (sessionDomain) { | ||
assert(isUrl(sessionDomain), 'sessionDomain parameter is not a valid URL'); | ||
this._setSessionServiceUrl(sessionDomain); | ||
} | ||
// Internal hack: set to false to always refresh from hassession | ||
this._enableSessionCaching = true; | ||
// Internal hack: set to true if the SDK is being used inside the ITP iframe to | ||
// avoid using the hasSession service and to prevent infinite iframe recursion | ||
this._itpMode = false; | ||
// Old session | ||
this._session = {}; | ||
this._setSessionServiceUrl(sessionDomain); | ||
this._setSpidServerUrl(env); | ||
this._setBffServerUrl(env); | ||
this._setOauthServerUrl(env); | ||
this._setHasSessionServerUrl(env); | ||
this._setGlobalSessionServiceUrl(env); | ||
@@ -156,3 +132,3 @@ } | ||
assert(isStr(url), `url parameter is invalid: ${url}`); | ||
this._spid = new JSONPClient({ | ||
this._spid = new RESTClient({ | ||
serverUrl: urlMapper(url, ENDPOINTS.SPiD), | ||
@@ -206,3 +182,3 @@ log: this.log, | ||
log: this.log, | ||
defaultParams: { client_sdrn, redirect_uri: this.redirectUri }, | ||
defaultParams: { client_sdrn, redirect_uri: this.redirectUri, sdk_version: version }, | ||
}); | ||
@@ -223,3 +199,3 @@ } | ||
log: this.log, | ||
defaultParams: { client_sdrn }, | ||
defaultParams: { client_sdrn, sdk_version: version }, | ||
}); | ||
@@ -229,17 +205,2 @@ } | ||
/** | ||
* Set HasSession server URL - real URL or 'PRE' style key | ||
* @private | ||
* @param {string} url | ||
* @returns {void} | ||
*/ | ||
_setHasSessionServerUrl(url) { | ||
assert(isStr(url), `url parameter is invalid: ${url}`); | ||
this._hasSession = new JSONPClient({ | ||
serverUrl: urlMapper(url, ENDPOINTS.HAS_SESSION), | ||
log: this.log, | ||
defaultParams: { client_id: this.clientId, redirect_uri: this.redirectUri }, | ||
}); | ||
} | ||
/** | ||
* Emits the relevant events based on the previous and new reply from hassession | ||
@@ -413,16 +374,22 @@ * @private | ||
/** | ||
* Check if we need to use the ITP workaround for Safari versions >= 12 | ||
* @private | ||
* @returns {boolean} | ||
* Log used settings and version | ||
* @throws {SDKError} - If log method is not provided | ||
* @return {void} | ||
*/ | ||
_itpModalRequired() { | ||
if (!document.requestStorageAccess || this._sessionService) { | ||
return false; | ||
logSettings() { | ||
if (!this.log && !window.console) { | ||
throw new SDKError('You have to provide log method in constructor'); | ||
} | ||
const safariVersion = navigator.userAgent.match(/Version\/(\d+)\./); | ||
if (!safariVersion || safariVersion.length < 2) { | ||
return false; | ||
const log = this.log || console.log; | ||
const settings = { | ||
clientId: this.clientId, | ||
redirectUri: this.redirectUri, | ||
env: this.env, | ||
sessionDomain: this._sessionDomain, | ||
sdkVersion: version | ||
} | ||
return parseInt(safariVersion[1], 10) >= 12; | ||
log(`Schibsted account SDK for browsers settings: \n${JSON.stringify(settings, null, 2)}`); | ||
} | ||
@@ -433,7 +400,3 @@ | ||
* @description When we send a request to this endpoint, cookies sent along with the request | ||
* determines the status of the user. If the user is not currently logged in, but has a cookie | ||
* with the "Remember me" flag switched on, calling this function will attempt to automatically | ||
* perform a login on the user | ||
* @param {boolean} [autologin=true] - Set this to `false` if you do **not** want the auto-login | ||
* to happen | ||
* determines the status of the user. | ||
* @throws {SDKError} - If the call to the hasSession service fails in any way (this will happen | ||
@@ -451,10 +414,6 @@ * if, say, the user is not logged in) | ||
*/ | ||
hasSession(autologin = true) { | ||
hasSession() { | ||
if (this._hasSessionInProgress) { | ||
return this._hasSessionInProgress; | ||
} | ||
if (typeof autologin !== 'boolean') { | ||
const [type, value] = inspect(autologin); | ||
return Promise.reject(new SDKError(`Parameter 'autologin' must be boolean, was: "${type}:${value}"`)); | ||
} | ||
const _postProcess = (sessionData) => { | ||
@@ -478,41 +437,12 @@ if (sessionData.error) { | ||
let sessionData = null; | ||
if (this._sessionService) { | ||
try { | ||
sessionData = await this._sessionService.get('/session'); | ||
} catch (err) { | ||
if (this.siteSpecificLogout) { | ||
if (err && err.code === 400 && this._enableSessionCaching) { | ||
const expiresIn = 1000 * (err.expiresIn || 300); | ||
this.cache.set(HAS_SESSION_CACHE_KEY, { error: err }, expiresIn); | ||
} | ||
// Don't fallback to other sources for user session lookup | ||
throw err; | ||
} | ||
// The session-service returns 400 if no session-cookie is sent in the | ||
// request. This will be the case if the user hasn't logged in since the | ||
// site switched to using the session-service. If the request contains a | ||
// session-cookie but no session is found (return code will be 404), then we | ||
// *should* throw an exception and *not* fall through to spid-hassession | ||
if (err.code !== 400) { | ||
throw err; | ||
} | ||
try { | ||
sessionData = await this._sessionService.get('/session'); | ||
} catch (err) { | ||
if (err && err.code === 400 && this._enableSessionCaching) { | ||
const expiresIn = 1000 * (err.expiresIn || 300); | ||
this.cache.set(HAS_SESSION_CACHE_KEY, { error: err }, expiresIn); | ||
} | ||
throw err; | ||
} | ||
const autoLoginConverted = autologin ? 1 : 0; | ||
if (!sessionData && !this._itpMode) { | ||
sessionData = await this._hasSession.get('rpc/hasSession.js', { autologin: autoLoginConverted }); | ||
} | ||
if (this._itpMode || (sessionData && isObject(sessionData.error) && sessionData.error.type === 'LoginException')) { | ||
sessionData = await this._spid.get('ajax/hasSession.js', { autologin: autoLoginConverted }); | ||
} | ||
const shouldShowItpModal = this._itpModalRequired() && !this._itpMode && sessionData && isObject(sessionData.error) | ||
&& sessionData.error.type === 'UserException' && this.cache.get(LOGIN_IN_PROGRESS_KEY) !== null; | ||
if (shouldShowItpModal) { | ||
this.cache.delete(LOGIN_IN_PROGRESS_KEY); | ||
const modal = new ItpModal(this._spid, this.clientId, this.redirectUri, this.env); | ||
sessionData = await modal.show() | ||
} | ||
if (sessionData && this._enableSessionCaching) { | ||
@@ -680,4 +610,10 @@ const expiresIn = 1000 * (sessionData.expiresIn || 300); | ||
* @param {string} [options.acrValues] - Authentication Context Class Reference Values. If | ||
* omitted, the user will be asked to authenticate using username+password. 'otp-email' means | ||
* one time password using email. 'otp-sms' means one time password using sms | ||
* omitted, the user will be asked to authenticate using username+password. | ||
* For 2FA (Two-Factor Authentication) possible values are `sms`, `otp` (one time password) and | ||
* `password` (will force password confirmation, even if user is already logged in). Those values might | ||
* be mixed as space-separated string. To make sure that user has authenticated with 2FA you need | ||
* to verify AMR (Authentication Methods References) claim in ID token. | ||
* Might also be used to ensure additional acr (sms, otp) for already logged in users. | ||
* Supported values are also 'otp-email' means one time password using email, and 'otp-sms' means | ||
* one time password using sms. | ||
* @param {string} options.state - An opaque value used by the client to maintain state between | ||
@@ -695,4 +631,2 @@ * the request and callback. It's also recommended to prevent CSRF | ||
* @param {boolean} [options.preferPopup=false] - Should we try to open a popup window? | ||
* @param {boolean} [options.newFlow=true] - Should we try the new GDPR-safe flow or the | ||
* legacy/stable SPiD flow? | ||
* @param {string} [options.loginHint=''] - user email or UUID hint | ||
@@ -717,3 +651,2 @@ * @param {string} [options.tag=''] - Pulse tag | ||
preferPopup = false, | ||
newFlow = true, | ||
loginHint = '', | ||
@@ -728,7 +661,5 @@ tag = '', | ||
this.cache.delete(HAS_SESSION_CACHE_KEY); | ||
const url = this.loginUrl({ state, acrValues, scope, redirectUri, newFlow, loginHint, tag, | ||
const url = this.loginUrl({ state, acrValues, scope, redirectUri, loginHint, tag, | ||
teaser, maxAge, locale, oneStepLogin }); | ||
this.showItpModalUponReturning(); | ||
if (preferPopup) { | ||
@@ -766,8 +697,5 @@ this.popup = | ||
* username+password. If set to `'otp-email'`, then passwordless login using email is used. If | ||
* `'otp-sms'`, then passwordless login using sms is used. Please note that this parameter has | ||
* no effect if `newFlow` is false | ||
* `'otp-sms'`, then passwordless login using sms is used. | ||
* @param {string} [options.scope='openid'] | ||
* @param {string} [options.redirectUri=this.redirectUri] | ||
* @param {boolean} [options.newFlow=true] - Should we try the new flow or the old Schibsted account | ||
* login? If this parameter is set to false, the `acrValues` parameter doesn't have any effect | ||
* @param {string} [options.loginHint=''] - user email or UUID hint | ||
@@ -791,3 +719,2 @@ * @param {string} [options.tag=''] - Pulse tag | ||
redirectUri = this.redirectUri, | ||
newFlow = true, | ||
loginHint = '', | ||
@@ -806,9 +733,9 @@ tag = '', | ||
redirectUri = arguments[3] || redirectUri; | ||
newFlow = typeof arguments[4] === 'boolean' ? arguments[4] : newFlow; | ||
loginHint = arguments[5] || loginHint; | ||
tag = arguments[6] || tag; | ||
teaser = arguments[7] || teaser; | ||
maxAge = isNaN(arguments[8]) ? maxAge : arguments[8]; | ||
loginHint = arguments[4] || loginHint; | ||
tag = arguments[5] || tag; | ||
teaser = arguments[6] || teaser; | ||
maxAge = isNaN(arguments[7]) ? maxAge : arguments[7]; | ||
} | ||
assert(!acrValues || isStrIn(acrValues, ['', 'otp-email', 'otp-sms'], true), | ||
const isValidAcrValue = (acrValue) => isStrIn(acrValue, ['password', 'otp', 'sms'], true); | ||
assert(!acrValues || isStrIn(acrValues, ['', 'otp-email', 'otp-sms'], true) || acrValues.split(' ').every(isValidAcrValue), | ||
`The acrValues parameter is not acceptable: ${acrValues}`); | ||
@@ -820,32 +747,16 @@ assert(isUrl(redirectUri), | ||
if (newFlow) { | ||
return this._oauthService.makeUrl('oauth/authorize', { | ||
response_type: 'code', | ||
'new-flow': true, | ||
redirect_uri: redirectUri, | ||
scope, | ||
state, | ||
acr_values: acrValues, | ||
login_hint: loginHint, | ||
tag, | ||
teaser, | ||
max_age: maxAge, | ||
locale, | ||
one_step_login: oneStepLogin || '', | ||
prompt: this.siteSpecificLogout ? 'select_account' : '' | ||
}); | ||
} else { | ||
// acrValues do not work with the old flows | ||
return this._spid.makeUrl('flow/login', { | ||
response_type: 'code', | ||
redirect_uri: redirectUri, | ||
scope, | ||
state, | ||
email: loginHint, | ||
tag, | ||
teaser, | ||
locale | ||
}); | ||
} | ||
return this._oauthService.makeUrl('oauth/authorize', { | ||
response_type: 'code', | ||
redirect_uri: redirectUri, | ||
scope, | ||
state, | ||
acr_values: acrValues, | ||
login_hint: loginHint, | ||
tag, | ||
teaser, | ||
max_age: maxAge, | ||
locale, | ||
one_step_login: oneStepLogin || '', | ||
prompt: !acrValues ? 'select_account' : '' | ||
}); | ||
} | ||
@@ -861,9 +772,5 @@ | ||
const params = { redirect_uri: redirectUri }; | ||
if (this._sessionService && this.siteSpecificLogout) { | ||
return this._sessionService.makeUrl('logout', params); | ||
} | ||
return this._spid.makeUrl('logout', Object.assign({ response_type: 'code' }, params)); | ||
return this._sessionService.makeUrl('logout', params); | ||
} | ||
/** | ||
@@ -894,75 +801,2 @@ * The account summary page url | ||
/** | ||
* Url to render either signup or login | ||
* @see https://techdocs.spid.no/flows/auth-flow/ | ||
* @param {string} [redirectUri=this.redirectUri] | ||
* @return {string} - the url to the authentication page | ||
*/ | ||
authFlowUrl(redirectUri = this.redirectUri) { | ||
assert(isUrl(redirectUri), `authFlowUrl(): redirectUri is invalid`); | ||
return this._spid.makeUrl('flow/auth', { | ||
response_type: 'code', | ||
redirect_uri: redirectUri | ||
}); | ||
} | ||
/** | ||
* Url to render a signup view and let the user login with credentials | ||
* @see https://techdocs.spid.no/flows/auth-flow/ | ||
* @param {string} [redirectUri=this.redirectUri] | ||
* @return {string} - the url to the signup page | ||
*/ | ||
signupFlowUrl(redirectUri = this.redirectUri) { | ||
assert(isUrl(redirectUri), `signupFlowUrl(): redirectUri is invalid`); | ||
return this._spid.makeUrl('flow/signup', { | ||
response_type: 'code', | ||
redirect_uri: redirectUri | ||
}); | ||
} | ||
/** | ||
* To render a signin view and let the user login without credentials | ||
* @see https://techdocs.spid.no/flows/auth-flow/ | ||
* @param {string} [redirectUri=this.redirectUri] | ||
* @return {string} - the url to the signin page | ||
*/ | ||
signinFlowUrl(redirectUri = this.redirectUri) { | ||
assert(isUrl(redirectUri), `signinFlowUrl(): redirectUri is invalid`); | ||
return this._spid.makeUrl('flow/signin', { | ||
response_type: 'code', | ||
redirect_uri: redirectUri | ||
}); | ||
} | ||
/** | ||
* Call this method immediately before sending a user to a Schibsted account flow if you | ||
* want to enable showing of the ITP modal upon returning to your site. You should | ||
* send the user to a Schibsted account flow immediately after calling this method without | ||
* invoking hasSession() again. | ||
* | ||
* Calling this is not required if you send the user to Schibsted account via the login() | ||
* method. | ||
* | ||
* @return {void} | ||
*/ | ||
showItpModalUponReturning() { | ||
// for safari, remember that we've got a login in progress so we can | ||
// work around some ITP issues when we come back from Schibsted Account | ||
if (this._itpModalRequired()) { | ||
this.cache.set(LOGIN_IN_PROGRESS_KEY, {}, 1000 * 60 * 15); | ||
} | ||
} | ||
/** | ||
* When returning after performing a flow, this method can be called prior to calling | ||
* hasSession() if you're certain that the user did not successfully log in. This will | ||
* prevent the ITP modal from showing up erroneously. If you're unsure, don't call this | ||
* method. | ||
* | ||
* @return {void} | ||
*/ | ||
suppressItpModal() { | ||
this.cache.delete(LOGIN_IN_PROGRESS_KEY); | ||
} | ||
/** | ||
* Function responsible for loading and displaying simplified login widget. How often | ||
@@ -969,0 +803,0 @@ * widget will be display is up to you. Preferred way would be to show it once per user, |
@@ -11,3 +11,2 @@ /* Copyright 2018 Schibsted Products & Technology AS. Licensed under the terms of the MIT license. | ||
import EventEmitter from 'tiny-emitter'; | ||
import JSONPClient from './JSONPClient'; | ||
import RESTClient from './RESTClient'; | ||
@@ -17,5 +16,4 @@ import Cache from './cache'; | ||
import SDKError from './SDKError'; | ||
import { version } from '../package.json'; | ||
const DEFAULT_CACHE_NO_ACCESS = 10; // 10 seconds | ||
const DEFAULT_CACHE_HAS_ACCESS = 1 * 60 * 60; // 1 hour | ||
const globalWindow = () => window; | ||
@@ -61,3 +59,3 @@ | ||
assert(isStr(url), `url parameter is invalid: ${url}`); | ||
this._spid = new JSONPClient({ | ||
this._spid = new RESTClient({ | ||
serverUrl: urlMapper(url, ENDPOINTS.SPiD), | ||
@@ -80,3 +78,3 @@ defaultParams: { client_id: this.clientId, redirect_uri: this.redirectUri }, | ||
log: this.log, | ||
defaultParams: { client_sdrn, redirect_uri: this.redirectUri }, | ||
defaultParams: { client_sdrn, redirect_uri: this.redirectUri, sdk_version: version }, | ||
}); | ||
@@ -86,94 +84,2 @@ } | ||
/** | ||
* Checks if the user has access to a particular product | ||
* @param {string} productId | ||
* @param {string} spId - The spId that was obtained from {@link Identity#getSpId} | ||
* @throws {SDKError} - If a network call fails in any way (this will happen if, say, the user | ||
* is not logged in) | ||
* @returns {Object|null} The data object returned from Schibsted account (or `null` if the user | ||
* doesn't have access to the given product) | ||
*/ | ||
async hasProduct(productId, spId) { | ||
const cacheKey = `prd_${productId}_${spId}`; | ||
let data = this.cache.get(cacheKey); | ||
const shouldCache = !data; | ||
if (!data && this._sessionService) { | ||
try { | ||
data = await this._sessionService.get(`/hasProduct/${productId}`); | ||
} catch (err) { | ||
// The session-service returns 400 if no session-cookie is sent in the request. This | ||
// will be the case if the user hasn't logged in since the site switched to using | ||
// the session-service. If the request contains a session-cookie but an error is | ||
// still thrown, then we *should* throw an exception and *not* fall through to | ||
// spid | ||
if (err.code !== 400) { | ||
throw err; | ||
} | ||
data = null; | ||
} | ||
} | ||
if (!data) { | ||
const params = { product_id: productId } | ||
if (spId) { | ||
params.sp_id = spId; | ||
} | ||
data = await this._spid.get('ajax/hasproduct.js', params); | ||
} | ||
if (shouldCache) { | ||
const expiresSeconds = data.result ? DEFAULT_CACHE_HAS_ACCESS : DEFAULT_CACHE_NO_ACCESS; | ||
this.cache.set(cacheKey, data, expiresSeconds * 1000); | ||
} | ||
if (!data.result) { | ||
return null; | ||
} | ||
this.emit('hasProduct', { productId, data }); | ||
return data; | ||
} | ||
/** | ||
* Checks if the user has access to a particular subscription | ||
* @param {string} subscriptionId | ||
* @param {string} spId - The spId that was obtained from {@link Identity#getSpId} | ||
* @throws {SDKError} - If a network call fails in any way (this will happen if, say, the user | ||
* is not logged in) | ||
* @returns {Object|null} The data object returned from Schibsted account (or `null` if the user | ||
* doesn't have access to the given subscription) | ||
*/ | ||
async hasSubscription(subscriptionId, spId) { | ||
const cacheKey = `sub_${subscriptionId}_${spId}`; | ||
let data = this.cache.get(cacheKey); | ||
const shouldCache = !data; | ||
if (!data && this._sessionService) { | ||
try { | ||
data = await this._sessionService.get(`/hasSubscription/${subscriptionId}`); | ||
} catch (err) { | ||
// The session-service returns 400 if no session-cookie is sent in the request. This | ||
// will be the case if the user hasn't logged in since the site switched to using | ||
// the session-service. If the request contains a session-cookie but an error is | ||
// still thrown, then we *should* throw an exception and *not* fall through to | ||
// spid | ||
if (err.code !== 400) { | ||
throw err; | ||
} | ||
data = null; | ||
} | ||
} | ||
if (!data) { | ||
const params = { product_id: subscriptionId } | ||
if (spId) { | ||
params.sp_id = spId; | ||
} | ||
data = await this._spid.get('ajax/hassubscription.js', params); | ||
} | ||
if (shouldCache) { | ||
const expiresSeconds = data.result ? DEFAULT_CACHE_HAS_ACCESS : DEFAULT_CACHE_NO_ACCESS; | ||
this.cache.set(cacheKey, data, expiresSeconds * 1000); | ||
} | ||
if (!data.result) { | ||
return null; | ||
} | ||
this.emit('hasSubscription', { subscriptionId, data }); | ||
return data; | ||
} | ||
/** | ||
* Checks if the user has access to a set of products or features. | ||
@@ -180,0 +86,0 @@ * @param {array} productIds - which products/features to check |
@@ -10,3 +10,2 @@ /* Copyright 2018 Schibsted Products & Technology AS. Licensed under the terms of the MIT license. | ||
import { ENDPOINTS } from './config'; | ||
import JSONPClient from './JSONPClient'; | ||
import * as popup from './popup'; | ||
@@ -50,3 +49,3 @@ import RESTClient from './RESTClient'; | ||
assert(isStr(url), `url parameter is invalid: ${url}`); | ||
this._spid = new JSONPClient({ | ||
this._spid = new RESTClient({ | ||
serverUrl: urlMapper(url, ENDPOINTS.SPiD), | ||
@@ -53,0 +52,0 @@ defaultParams: { client_id: this.clientId, redirect_uri: this.redirectUri }, |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
7726448
1
47400
18
16
51
456
- Removedfetch-jsonp@^1.1.3
- Removedfetch-jsonp@1.3.0(transitive)