Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Accept characteristics on shield rule #1989

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
29 changes: 22 additions & 7 deletions arcjet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
ArcjetDenyDecision,
ArcjetErrorDecision,
} from "@arcjet/protocol";
import { isRateLimitRule } from "@arcjet/protocol/convert.js";
import { isRateLimitRule, isShieldRule } from "@arcjet/protocol/convert.js";
import type { Client } from "@arcjet/protocol/client.js";
import * as analyze from "@arcjet/analyze";
import type {
Expand Down Expand Up @@ -439,7 +439,14 @@ const validateBotOptions = createValidator({

const validateShieldOptions = createValidator({
rule: "shield",
validations: [{ key: "mode", required: false, validate: validateMode }],
validations: [
{ key: "mode", required: false, validate: validateMode },
{
key: "characteristics",
validate: validateStringArray,
required: false,
},
],
});

type TokenBucketRateLimitOptions<Characteristics extends readonly string[]> = {
Expand Down Expand Up @@ -1074,19 +1081,27 @@ export function detectBot(options: BotOptions): Primitive<{}> {
];
}

export type ShieldOptions = {
export type ShieldOptions<Characteristics extends readonly string[]> = {
mode?: ArcjetMode;
characteristics?: Characteristics;
};

export function shield(options: ShieldOptions): Primitive<{}> {
export function shield<const Characteristics extends readonly string[] = []>(
options: ShieldOptions<Characteristics>,
): Primitive<Simplify<CharacteristicProps<Characteristics>>> {
validateShieldOptions(options);

const mode = options.mode === "LIVE" ? "LIVE" : "DRY_RUN";
const characteristics = Array.isArray(options.characteristics)
? options.characteristics
: undefined;

return [
<ArcjetShieldRule<{}>>{
type: "SHIELD",
priority: Priority.Shield,
mode,
characteristics,
},
];
}
Expand Down Expand Up @@ -1300,10 +1315,10 @@ export default function arcjet<
reason: new ArcjetReason(),
});

// Add top-level characteristics to all Rate Limit rules that don't already have
// their own set of characteristics.
// Add top-level characteristics to all RateLimit or Shield rules that
// don't already have their own set of characteristics.
const candidate_rule = rules[idx];
if (isRateLimitRule(candidate_rule)) {
if (isRateLimitRule(candidate_rule) || isShieldRule(candidate_rule)) {
if (typeof candidate_rule.characteristics === "undefined") {
candidate_rule.characteristics = characteristics;
rules[idx] = candidate_rule;
Expand Down
3 changes: 2 additions & 1 deletion protocol/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ function isEmailRule<Props extends { email: string }>(
return rule.type === "EMAIL";
}

function isShieldRule<Props extends { email: string }>(
export function isShieldRule<Props extends { email: string }>(
rule: ArcjetRule<Props>,
): rule is ArcjetShieldRule<Props> {
return rule.type === "SHIELD";
Expand Down Expand Up @@ -652,6 +652,7 @@ export function ArcjetRuleToProtocol<Props extends { [key: string]: unknown }>(
case: "shield",
value: {
mode: ArcjetModeToProtocol(rule.mode),
characteristics: rule.characteristics,
autoAdded: false,
},
},
Expand Down
2 changes: 2 additions & 0 deletions protocol/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,8 @@ export interface ArcjetBotRule<Props extends {}>

export interface ArcjetShieldRule<Props extends {}> extends ArcjetRule<Props> {
type: "SHIELD";

characteristics?: string[];
}

export interface ArcjetLogger {
Expand Down
18 changes: 14 additions & 4 deletions protocol/proto/decide/v1alpha1/decide_pb.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1018,10 +1018,10 @@ export declare class RateLimitRule extends Message<RateLimitRule> {
match: string;

/**
* Defines how Arcjet will track the rate limit. If not specified, it will
* default to the client IP address ip.src. If more than one option is
* provided, they will be combined. See
* https://docs.arcjet.com/rate-limiting/configuration
* Defines how Arcjet will track rate limits. If none are specified, it will
* default to using the client IP address. If more than one characteristic
* is provided, they will be combined. For further details, see
* https://docs.arcjet.com/architecture/#fingerprinting
*
* @generated from field: repeated string characteristics = 3;
*/
Expand Down Expand Up @@ -1330,6 +1330,16 @@ export declare class ShieldRule extends Message<ShieldRule> {
*/
autoAdded: boolean;

/**
* Defines how Arcjet will track suspicious requests. If none are specified,
* it will default to using the client IP address. If more than one
* characteristic is provided, they will be combined. For further details,
* see https://docs.arcjet.com/architecture/#fingerprinting
*
* @generated from field: repeated string characteristics = 3;
*/
characteristics: string[];

constructor(data?: PartialMessage<ShieldRule>);

static readonly runtime: typeof proto3;
Expand Down
1 change: 1 addition & 0 deletions protocol/proto/decide/v1alpha1/decide_pb.js
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ export const ShieldRule = /*@__PURE__*/ proto3.makeMessageType(
() => [
{ no: 1, name: "mode", kind: "enum", T: proto3.getEnumType(Mode) },
{ no: 2, name: "auto_added", kind: "scalar", T: 8 /* ScalarType.BOOL */ },
{ no: 3, name: "characteristics", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true },
],
);

Expand Down