Socket
Socket
Sign inDemoInstall

@sanity/preview-url-secret

Package Overview
Dependencies
Maintainers
56
Versions
72
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@sanity/preview-url-secret - npm Package Compare versions

Comparing version 1.6.19 to 1.6.20-canary.1

7

CHANGELOG.md

@@ -9,2 +9,9 @@ <!-- markdownlint-disable --><!-- textlint-disable -->

## [1.6.20](https://github.com/sanity-io/visual-editing/compare/preview-url-secret-v1.6.19...preview-url-secret-v1.6.20) (2024-08-12)
### Bug Fixes
* **deps:** update dependency @sanity/client to v6.21.2 ([#1749](https://github.com/sanity-io/visual-editing/issues/1749)) ([b9efdd2](https://github.com/sanity-io/visual-editing/commit/b9efdd2a672fdef518bc22a29a25992c938ba1ef))
## [1.6.19](https://github.com/sanity-io/visual-editing/compare/preview-url-secret-v1.6.18...preview-url-secret-v1.6.19) (2024-08-02)

@@ -11,0 +18,0 @@

25

dist/_chunks-es/constants.js

@@ -1,1 +0,24 @@

const e="sanity.previewUrlSecret",t="sanity-preview-url-secret",s="2023-11-09",a="sanity-preview-secret",r="sanity-preview-pathname",i="development"===process.env.NODE_ENV,n=3600,d=`*[_type == "${e}" && secret == $secret && dateTime(_updatedAt) > dateTime(now()) - 3600][0]{\n _id,\n _updatedAt,\n secret,\n studioUrl,\n}`,p=`*[_type == "${e}" && dateTime(_updatedAt) <= dateTime(now()) - 3600]`,c="sanity.preview-url-secret";export{n as S,s as a,r as b,e as c,p as d,d as f,i,t as s,c as t,a as u};//# sourceMappingURL=constants.js.map
const schemaType = "sanity.previewUrlSecret";
const schemaIdPrefix = "sanity-preview-url-secret";
const apiVersion = "2023-11-09";
const urlSearchParamPreviewSecret = "sanity-preview-secret";
const urlSearchParamPreviewPathname = "sanity-preview-pathname";
const isDev = process.env.NODE_ENV === "development";
const SECRET_TTL = 60 * 60;
const fetchSecretQuery = (
/* groq */
`*[_type == "${schemaType}" && secret == $secret && dateTime(_updatedAt) > dateTime(now()) - ${SECRET_TTL}][0]{
_id,
_updatedAt,
secret,
studioUrl,
}`
);
const deleteExpiredSecretsQuery = (
/* groq */
`*[_type == "${schemaType}" && dateTime(_updatedAt) <= dateTime(now()) - ${SECRET_TTL}]`
);
const tag = "sanity.preview-url-secret";
export { SECRET_TTL, apiVersion, deleteExpiredSecretsQuery, fetchSecretQuery, isDev, schemaIdPrefix, schemaType, tag, urlSearchParamPreviewPathname, urlSearchParamPreviewSecret };
//# sourceMappingURL=constants.js.map

@@ -1,1 +0,33 @@

import{uuid as t}from"@sanity/uuid";import{a as e,S as a,s as r,c as n,t as s,d as o}from"./_chunks-es/constants.js";async function c(c,i,p,u,l=t()){const y=c.withConfig({apiVersion:e});try{const t=new Date(Date.now()+1e3*a),e=`${r}.${l}`,o=function(){if(typeof crypto<"u"){const t=new Uint8Array(16);crypto.getRandomValues(t);let e="";for(let a=0;a<t.length;a++)e+=t[a].toString(16).padStart(2,"0");return e=btoa(e).replace(/\+/g,"-").replace(/\//g,"_").replace(/[=]+$/,""),e}return Math.random().toString(36).slice(2)}(),c=y.patch(e).set({secret:o,source:i,studioUrl:p,userId:u});return await y.transaction().createOrReplace({_id:e,_type:n}).patch(c).commit({tag:s}),{secret:o,expiresAt:t}}finally{await y.delete({query:o})}}export{c as createPreviewSecret};//# sourceMappingURL=create-secret.js.map
import { uuid } from '@sanity/uuid';
import { apiVersion, SECRET_TTL, schemaIdPrefix, schemaType, tag, deleteExpiredSecretsQuery } from './_chunks-es/constants.js';
function generateUrlSecret() {
if (typeof crypto !== "undefined") {
const array = new Uint8Array(16);
crypto.getRandomValues(array);
let key = "";
for (let i = 0; i < array.length; i++) {
key += array[i].toString(16).padStart(2, "0");
}
key = btoa(key).replace(/\+/g, "-").replace(/\//g, "_").replace(/[=]+$/, "");
return key;
}
return Math.random().toString(36).slice(2);
}
async function createPreviewSecret(_client, source, studioUrl, userId, id = uuid()) {
const client = _client.withConfig({ apiVersion });
try {
const expiresAt = new Date(Date.now() + 1e3 * SECRET_TTL);
const _id = `${schemaIdPrefix}.${id}`;
const newSecret = generateUrlSecret();
const patch = client.patch(_id).set({ secret: newSecret, source, studioUrl, userId });
await client.transaction().createOrReplace({ _id, _type: schemaType }).patch(patch).commit({ tag });
return { secret: newSecret, expiresAt };
} finally {
await client.delete({ query: deleteExpiredSecretsQuery });
}
}
export { createPreviewSecret };
//# sourceMappingURL=create-secret.js.map

@@ -1,1 +0,49 @@

import{u as e,b as t}from"./_chunks-es/constants.js";function r(r){const{draftMode:n,previewMode:a,origin:o=(typeof location>"u"?"https://localhost":location.origin)}=r,i=(null==a?void 0:a.enable)||(null==n?void 0:n.enable);let{preview:s="/"}=r;const c=new URL(s,o),h=i?new URL(i,o):void 0;return async n=>{try{if(n.previewSearchParam){const e=new URL(n.previewSearchParam,c);e.origin===c.origin&&(s=`${e.pathname}${e.search}`)}else if(n.referrer){const e=new URL(n.referrer);e.origin===c.origin&&(s=`${e.pathname}${e.search}`)}}catch{}typeof location<"u"&&location.origin===c.origin&&n.studioBasePath&&(s.startsWith(`${n.studioBasePath}/`)||s===n.studioBasePath)&&(s=r.preview||"/");const a=new URL(s,c);if(h){const r=new URL(h),{searchParams:o}=r;return o.set(e,n.previewUrlSecret),a.pathname!==r.pathname&&o.set(t,`${a.pathname}${a.search}`),r.toString()}return a.toString()}}export{r as definePreviewUrl};//# sourceMappingURL=define-preview-url.js.map
import { urlSearchParamPreviewSecret, urlSearchParamPreviewPathname } from './_chunks-es/constants.js';
function definePreviewUrl(options) {
const {
draftMode,
previewMode,
origin = typeof location === "undefined" ? "https://localhost" : location.origin
} = options;
const enableUrl = previewMode?.enable || draftMode?.enable;
let { preview = "/" } = options;
const productionUrl = new URL(preview, origin);
const enablePreviewModeUrl = enableUrl ? new URL(enableUrl, origin) : void 0;
return async (context) => {
try {
if (context.previewSearchParam) {
const restoredUrl = new URL(context.previewSearchParam, productionUrl);
if (restoredUrl.origin === productionUrl.origin) {
preview = `${restoredUrl.pathname}${restoredUrl.search}`;
}
} else if (context.referrer) {
const referrerUrl = new URL(context.referrer);
if (referrerUrl.origin === productionUrl.origin) {
preview = `${referrerUrl.pathname}${referrerUrl.search}`;
}
}
} catch {
}
if (typeof location !== "undefined" && location.origin === productionUrl.origin && context.studioBasePath && (preview.startsWith(`${context.studioBasePath}/`) || preview === context.studioBasePath)) {
preview = options.preview || "/";
}
const previewUrl = new URL(preview, productionUrl);
if (enablePreviewModeUrl) {
const enablePreviewModeRequestUrl = new URL(enablePreviewModeUrl);
const { searchParams } = enablePreviewModeRequestUrl;
searchParams.set(urlSearchParamPreviewSecret, context.previewUrlSecret);
if (previewUrl.pathname !== enablePreviewModeRequestUrl.pathname) {
searchParams.set(
urlSearchParamPreviewPathname,
`${previewUrl.pathname}${previewUrl.search}`
);
}
return enablePreviewModeRequestUrl.toString();
}
return previewUrl.toString();
};
}
export { definePreviewUrl };
//# sourceMappingURL=define-preview-url.js.map

@@ -1,1 +0,11 @@

import{b as s}from"./_chunks-es/constants.js";function r(r){return r.searchParams.has(s)?new URL(r.searchParams.get(s),r.origin):r}export{r as getRedirectTo};//# sourceMappingURL=get-redirect-to.js.map
import { urlSearchParamPreviewPathname } from './_chunks-es/constants.js';
function getRedirectTo(url) {
if (url.searchParams.has(urlSearchParamPreviewPathname)) {
return new URL(url.searchParams.get(urlSearchParamPreviewPathname), url.origin);
}
return url;
}
export { getRedirectTo };
//# sourceMappingURL=get-redirect-to.js.map

@@ -1,1 +0,98 @@

import{a as e,u as t,b as r,f as i,t as s,i as o}from"./_chunks-es/constants.js";async function n(n,a,l="Cloudflare-Workers"===(e=>null==(e=globalThis.navigator)?void 0:e.userAgent)()){const c=function(t){if(!t)throw new TypeError("`client` is required");if(!t.config().token)throw new TypeError("`client` must have a `token` specified");return t.withConfig({apiVersion:e,useCdn:!1,perspective:"published",resultSourceMap:!1,stega:!1})}(n);let u;try{u=function(e){const i=new URL(e,"http://localhost"),s=i.searchParams.get(t);if(!s)throw new Error("Missing secret");let o;const n=i.searchParams.get(r);if(n){const{pathname:e,search:t,hash:r}=new URL(n,"http://localhost");o=`${e}${t}${r}`}return{secret:s,redirectTo:o}}(a)}catch(e){return o&&console.error("Failed to parse preview URL",e,{previewUrl:a,client:c}),{isValid:!1}}const{isValid:d,studioUrl:h}=await async function(e,t,r){if(typeof EdgeRuntime<"u"&&await new Promise((e=>setTimeout(e,300))),!t||!t.trim())return{isValid:!1,studioUrl:null};const o=await e.fetch(i,{secret:t},{tag:s,...r?void 0:{cache:"no-store"}});return null!=o&&o._id&&null!=o&&o._updatedAt&&null!=o&&o.secret?{isValid:t===o.secret,studioUrl:o.studioUrl}:{isValid:!1,studioUrl:null}}(c,u.secret,l),p=d?u.redirectTo:void 0;let f;if(d)try{f=new URL(h).origin}catch(e){o&&console.error("Failed to parse studioUrl",e,{previewUrl:a,studioUrl:h})}return{isValid:d,redirectTo:p,studioOrigin:f}}export{r as urlSearchParamPreviewPathname,t as urlSearchParamPreviewSecret,n as validatePreviewUrl};//# sourceMappingURL=index.js.map
import { apiVersion, urlSearchParamPreviewSecret, urlSearchParamPreviewPathname, fetchSecretQuery, tag, isDev } from './_chunks-es/constants.js';
function createClientWithConfig(client) {
if (!client) {
throw new TypeError("`client` is required");
}
if (!client.config().token) {
throw new TypeError("`client` must have a `token` specified");
}
return client.withConfig({
// Userland might be using an API version that's too old to use perspectives
apiVersion,
// We can't use the CDN, the secret is typically validated right after it's created
useCdn: false,
// The documents that hold secrets are never drafts
perspective: "published",
// Don't waste time returning a source map, we don't need it
resultSourceMap: false,
// @ts-expect-error - If stega is enabled, make sure it's disabled
stega: false
});
}
function parsePreviewUrl(unsafeUrl) {
const url = new URL(unsafeUrl, "http://localhost");
const secret = url.searchParams.get(urlSearchParamPreviewSecret);
if (!secret) {
throw new Error("Missing secret");
}
let redirectTo = void 0;
const unsafeRedirectTo = url.searchParams.get(urlSearchParamPreviewPathname);
if (unsafeRedirectTo) {
const { pathname, search, hash } = new URL(unsafeRedirectTo, "http://localhost");
redirectTo = `${pathname}${search}${hash}`;
}
return { secret, redirectTo };
}
async function validateSecret(client, secret, disableCacheNoStore) {
if (typeof EdgeRuntime !== "undefined") {
await new Promise((resolve) => setTimeout(resolve, 300));
}
if (!secret || !secret.trim()) {
return { isValid: false, studioUrl: null };
}
const result = await client.fetch(
fetchSecretQuery,
{ secret },
{
tag,
// In CloudFlare Workers we can't pass the cache header
...!disableCacheNoStore ? { cache: "no-store" } : void 0
}
);
if (!result?._id || !result?._updatedAt || !result?.secret) {
return { isValid: false, studioUrl: null };
}
return { isValid: secret === result.secret, studioUrl: result.studioUrl };
}
async function validatePreviewUrl(_client, previewUrl, disableCacheNoStore = globalThis.navigator?.userAgent === "Cloudflare-Workers") {
const client = createClientWithConfig(_client);
let parsedPreviewUrl;
try {
parsedPreviewUrl = parsePreviewUrl(previewUrl);
} catch (error) {
if (isDev) {
console.error("Failed to parse preview URL", error, {
previewUrl,
client
});
}
return { isValid: false };
}
const { isValid, studioUrl } = await validateSecret(
client,
parsedPreviewUrl.secret,
disableCacheNoStore
);
const redirectTo = isValid ? parsedPreviewUrl.redirectTo : void 0;
let studioOrigin;
if (isValid) {
try {
studioOrigin = new URL(studioUrl).origin;
} catch (error) {
if (isDev) {
console.error("Failed to parse studioUrl", error, {
previewUrl,
studioUrl
});
}
}
}
return { isValid, redirectTo, studioOrigin };
}
export { urlSearchParamPreviewPathname, urlSearchParamPreviewSecret, validatePreviewUrl };
//# sourceMappingURL=index.js.map

@@ -1,1 +0,84 @@

import{defineType as e,definePlugin as t}from"sanity";import{c as i,S as s}from"./_chunks-es/constants.js";import{LockIcon as n,CloseCircleIcon as r,CheckmarkCircleIcon as o}from"@sanity/icons";const a=e({type:"document",icon:n,name:i,title:"@sanity/preview-url-secret",readOnly:!0,fields:[{type:"string",name:"secret",title:"Secret"},{type:"string",name:"source",title:"Source Tool"},{type:"string",name:"studioUrl",title:"Studio URL"},{type:"string",name:"userId",title:"Sanity User ID"}],preview:{select:{source:"source",studioUrl:"studioUrl",updatedAt:"_updatedAt"},prepare(e){const t=e.studioUrl?new URL(e.studioUrl,location.origin):void 0,i=new Date(e.updatedAt).getTime(),n=new Date(i+1e3*s),a=n<new Date,c=a?r:o;return{title:t?`${t.host}${t.pathname}`:e.source,subtitle:a?"Expired":`Expires in ${Math.round((n.getTime()-Date.now())/6e4)} minutes`,media:c}}}}),c=t((()=>({name:"sanity-plugin-debug-secrets",schema:{types:[a]},document:{actions:(e,t)=>t.schemaType!==i?e:e.filter((({action:e})=>"delete"===e)),inspectors:(e,t)=>t.documentType!==i?e:[],unstable_fieldActions:(e,t)=>t.schemaType.name!==i?e:[]}})));export{c as debugSecrets};//# sourceMappingURL=sanity-plugin-debug-secrets.js.map
import { defineType, definePlugin } from 'sanity';
import { schemaType, SECRET_TTL } from './_chunks-es/constants.js';
import { LockIcon, CloseCircleIcon, CheckmarkCircleIcon } from '@sanity/icons';
const debugUrlSecretsType = defineType({
type: "document",
icon: LockIcon,
name: schemaType,
title: "@sanity/preview-url-secret",
readOnly: true,
fields: [
{
type: "string",
name: "secret",
title: "Secret"
},
{
type: "string",
name: "source",
title: "Source Tool"
},
{
type: "string",
name: "studioUrl",
title: "Studio URL"
},
{
type: "string",
name: "userId",
title: "Sanity User ID"
}
],
preview: {
select: {
source: "source",
studioUrl: "studioUrl",
updatedAt: "_updatedAt"
},
prepare(data) {
const url = data.studioUrl ? new URL(data.studioUrl, location.origin) : void 0;
const updatedAt = new Date(data.updatedAt).getTime();
const expiresAt = new Date(updatedAt + 1e3 * SECRET_TTL);
const expired = expiresAt < /* @__PURE__ */ new Date();
const icon = expired ? CloseCircleIcon : CheckmarkCircleIcon;
return {
title: url ? `${url.host}${url.pathname}` : data.source,
subtitle: expired ? "Expired" : `Expires in ${Math.round((expiresAt.getTime() - Date.now()) / (1e3 * 60))} minutes`,
media: icon
};
}
}
});
const debugSecrets = definePlugin(() => {
return {
name: "sanity-plugin-debug-secrets",
schema: {
types: [debugUrlSecretsType]
},
document: {
actions: (prev, context) => {
if (context.schemaType !== schemaType) {
return prev;
}
return prev.filter(({ action }) => action === "delete");
},
inspectors: (prev, context) => {
if (context.documentType !== schemaType) {
return prev;
}
return [];
},
unstable_fieldActions: (prev, context) => {
if (context.schemaType.name !== schemaType) {
return prev;
}
return [];
}
}
};
});
export { debugSecrets };
//# sourceMappingURL=sanity-plugin-debug-secrets.js.map

@@ -1,1 +0,20 @@

import{b as s,u as e}from"./_chunks-es/constants.js";function a(a){const r=new URL(a);return r.searchParams.delete(s),r.searchParams.delete(e),r}function r(s){return s.searchParams.has(e)}function n(a,r,n){const t=new URL(a);return t.searchParams.set(e,r),t.searchParams.set(s,n),t}export{r as hasSecretSearchParams,n as setSecretSearchParams,a as withoutSecretSearchParams};//# sourceMappingURL=without-secret-search-params.js.map
import { urlSearchParamPreviewPathname, urlSearchParamPreviewSecret } from './_chunks-es/constants.js';
function withoutSecretSearchParams(url) {
const newUrl = new URL(url);
newUrl.searchParams.delete(urlSearchParamPreviewPathname);
newUrl.searchParams.delete(urlSearchParamPreviewSecret);
return newUrl;
}
function hasSecretSearchParams(url) {
return url.searchParams.has(urlSearchParamPreviewSecret);
}
function setSecretSearchParams(url, secret, redirectTo) {
const newUrl = new URL(url);
newUrl.searchParams.set(urlSearchParamPreviewSecret, secret);
newUrl.searchParams.set(urlSearchParamPreviewPathname, redirectTo);
return newUrl;
}
export { hasSecretSearchParams, setSecretSearchParams, withoutSecretSearchParams };
//# sourceMappingURL=without-secret-search-params.js.map

30

package.json
{
"name": "@sanity/preview-url-secret",
"version": "1.6.19",
"version": "1.6.20-canary.1",
"homepage": "https://github.com/sanity-io/visual-editing/tree/main/packages/preview-url-secret#readme",

@@ -134,19 +134,19 @@ "bugs": {

"devDependencies": {
"@repo/channels": "0.4.0",
"@sanity/client": "^6.21.1",
"@sanity/icons": "^3.2.0",
"@sanity/pkg-utils": "6.9.3",
"@typescript-eslint/eslint-plugin": "^7.13.1",
"@typescript-eslint/parser": "^7.13.1",
"@sanity/client": "^6.21.3",
"@sanity/icons": "^3.4.0",
"@sanity/pkg-utils": "6.11.1",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-simple-import-sort": "^12.1.0",
"sanity": "^3.47.1",
"typescript": "5.4.5",
"vitest": "^1.6.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-simple-import-sort": "^12.1.1",
"sanity": "^3.57.2",
"typescript": "5.6.2",
"vitest": "^2.0.5",
"@repo/channels": "0.4.0",
"@repo/package.config": "0.0.0"
},
"peerDependencies": {
"@sanity/client": "^6.21.1"
"@sanity/client": "^6.21.3"
},

@@ -161,6 +161,6 @@ "engines": {

"build": "pkg build --strict --check --clean",
"dev": "pkg build --strict",
"lint": "eslint .",
"test": "vitest --pass-with-no-tests --typecheck",
"watch": "pkg watch --strict"
"test": "vitest --pass-with-no-tests --typecheck"
}
}

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc