Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.DS_Store
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ You can adjust the following settings when using this module:
-o, --allowed-origins, env: ALLOWED_ORIGINS
Value for Access-Control-Allow-Origin header
Default: "*" ("*" = all origins are allowed)

-a, --auth, env: AUTH
Add an `Authorization: ...` header to requests.
```

## Example usage
Expand All @@ -42,5 +45,6 @@ $ deno run --allow-net https://deno.land/x/cors_proxy/mod.ts \
--port 1337 \
--route / \
--allowed-urls https://duck.com,https://firefox.com \
--allowed-origins https://my-webapp.example.com
--allowed-origins https://my-webapp.example.com \
--auth "Bearer MY-BEARER-TOKEN"
```
51 changes: 42 additions & 9 deletions config.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,66 @@
export const config: {
[key: string]: {
default: string;
env: string;
argsShort: string;
argsLong: string;
};
} = {
export type ConfigUnit = {
default: string,
env: string,
argsShort: string,
argsLong: string,
type: "string" | "boolean"
}
const makeConfig = <T extends Record<string, ConfigUnit>>(c: T) => c;
export const config = makeConfig({
authToken: {
default: "",
env: "AUTH",
argsShort: "a",
argsLong: "auth",
type: "string",
},
port: {
default: "3000",
env: "PORT",
argsShort: "p",
argsLong: "port",
type: "string",
},
route: {
default: "/cors/",
env: "CORS_ROUTE_PREFIX",
argsShort: "r",
argsLong: "route",
type: "string",
},
allowedUrls: {
default: "",
env: "ALLOWED_URLS",
argsShort: "u",
argsLong: "allowed-urls",
type: "string",
},
allowedOrigins: {
default: "*",
env: "ALLOWED_ORIGINS",
argsShort: "o",
argsLong: "allowed-origins",
type: "string",
},
allowedHeaders: {
default: "",
env: "ALLOWED_HEADERS",
argsShort: "H",
argsLong: "allowed-headers",
type: "string",
},
allowedMethods: {
default: "GET, HEAD, POST",
env: "ALLOWED_METHODS",
argsShort: "M",
argsLong: "allowed-methods",
type: "string",
},
};
verbose: {
default: "true",
env: "VERBOSE",
argsShort: "v",
argsLong: "verbose",
type: "boolean",
}
});
5 changes: 5 additions & 0 deletions deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"imports": {
"@std/cli": "jsr:@std/cli@^0.224.7"
}
}
63 changes: 63 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 24 additions & 20 deletions helpers/allowed-urls-helper.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
/**
* Checks whether cors proxy server should serve the url
* @param url URL to check
* @param rules Comma separated list of rules (e.g. "https://duck.com,https://example.com")
* @param rules String array of rules, e.g. `["https://duck.com", "https://example.com"]`, or `undefined` to allow for
* any url (best to skip the function call instead).
*/
export function isUrlAllowed(url: string, rules: string): boolean {
if (rules !== "") {
const rulesList = rules.split(",");
return rulesList.some((rule) => {
/**
* a) rule without trailing slash matches exactly with url (e.g. rule: https://duck.com/, url: https://duck.com)
* b1) url starts with rule (including trailing slash; e.g. rule: https://example.com, url: https://example.com/path1)
* b2) url starts with rule without trailing slash (only if rule contains at least one slash for path
* (to avoid using rule as subdomain, e.g. https://duck.com.example.com/)
* e.g. rule: https://example.com/path1, url: https://example.com/path123)
*/
const ruleWithoutTrailingSlash = rule.endsWith("/")
? rule.substr(0, rule.length - 1)
: rule;
const ruleContainsPath = (rule.match(/\//g) || []).length >= 3;
return url === ruleWithoutTrailingSlash ||
export function isUrlAllowed(url: string, rules: string[]): boolean {
if (!rules || rules.length === 0) {
return true
}

return rules.some((rule) => {
// Catch empty string being passed in from split
if(rule === '') { return true }

/**
* a) rule without trailing slash matches exactly with url (e.g. rule: https://duck.com/, url: https://duck.com)
* b1) url starts with rule (including trailing slash; e.g. rule: https://example.com, url: https://example.com/path1)
* b2) url starts with rule without trailing slash (only if rule contains at least one slash for path
* (to avoid using rule as subdomain, e.g. https://duck.com.example.com/)
* e.g. rule: https://example.com/path1, url: https://example.com/path123)
*/
const ruleWithoutTrailingSlash = rule.endsWith("/")
? rule.substring(0, rule.length - 1)
: rule;
const ruleContainsPath = (rule.match(/\//g) || []).length >= 3;
return url === ruleWithoutTrailingSlash ||
url.startsWith(
rule + (ruleContainsPath || rule.endsWith("/") ? "" : "/"),
);
});
}
return true;
});
}
19 changes: 2 additions & 17 deletions helpers/conf-helper.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,9 @@
export function getValueFromArgs(
args: { [arg: string]: string | number },
flags: string[],
): string | undefined {
for (const flag of flags) {
if (args[flag]) {
return args[flag].toString();
}
}
return undefined;
}

let LOGGED_NO_ENV_PERMISSION_WARNING = false;
export function getValueFromEnv(arg: string): string | undefined {
try {
return String(Deno.env.get(arg));
return Deno.env.get(arg);
} catch (err) {
if (
err.message ===
"access to environment variables, run again with the --allow-env flag"
) {
if (err instanceof Deno.errors.NotCapable) {
if (!LOGGED_NO_ENV_PERMISSION_WARNING) {
console.warn(
`No access to environment variables. Run again with the --allow-env flag to use environment variables instead of defaults.`,
Expand Down
53 changes: 27 additions & 26 deletions mod.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,37 @@
import { parse } from "https://deno.land/std@0.74.0/flags/mod.ts";
import { getValueFromArgs, getValueFromEnv } from "./helpers/conf-helper.ts";
import { config } from "./config.ts";
import { parseArgs } from "jsr:@std/cli@^0.224.7";
import { getValueFromEnv } from "./helpers/conf-helper.ts";
import { config, ConfigUnit } from "./config.ts";
import { showHelpMessage } from "./helpers/help-helper.ts";
import { run } from "./server.ts";

const args = parse(Deno.args);
const configFlagsOfType = (config: Record<string, ConfigUnit>, type: ConfigUnit["type"]) =>
Object.entries(config)
.filter(([k, v]) => v.type === type)
.flatMap(([, {argsLong, argsShort}]) => [argsShort, argsLong])

const args = parseArgs(Deno.args, {
string: configFlagsOfType(config, "string"),
boolean: configFlagsOfType(config, "boolean")
});

if (args.help) {
showHelpMessage();
Deno.exit();
}

const port = Number(
getValueFromArgs(args, [config.port.argsLong, config.port.argsShort]) ||
getValueFromEnv(config.port.env) || config.port.default,
);
const corsRoutePrefix =
getValueFromArgs(args, [config.route.argsLong, config.route.argsShort]) ||
getValueFromEnv(config.route.env) || config.route.default;
const allowedUrls =
getValueFromArgs(
args,
[config.allowedUrls.argsLong, config.allowedUrls.argsShort],
) ||
getValueFromEnv(config.allowedUrls.env) ||
config.allowedUrls.default;
const allowedOrigins =
getValueFromArgs(
args,
[config.allowedOrigins.argsLong, config.allowedOrigins.argsShort],
) ||
getValueFromEnv(config.allowedOrigins.env) ||
config.allowedOrigins.default;
const configValue: <T>(c: ConfigUnit) => string = (configUnit) =>
args[configUnit.argsLong]
?? args[configUnit.argsShort]
?? getValueFromEnv(configUnit.env)
?? configUnit.default;

const port = Number(configValue(config.port));
const corsRoutePrefix = configValue(config.route);
const allowedUrls = configValue(config.allowedUrls);
const allowedOrigins = configValue(config.allowedOrigins).split(/\s*,\s*/);
const allowedHeaders = configValue(config.allowedHeaders);
const allowedMethods = configValue(config.allowedMethods);
const authToken = configValue(config.authToken);
const verbose = !!(args[config.verbose.argsLong] || args[config.verbose.argsShort]);

run(port, corsRoutePrefix, allowedUrls, allowedOrigins);
run(port, corsRoutePrefix, allowedUrls, allowedOrigins, allowedHeaders, allowedMethods, authToken, verbose);
Loading