๐ก ๋ธ๋ฆฌ์ธ ๊ฐ๋
13103 ๋จ์ด blitzjavascriptmonolithserverless
Blitz๊ฐ ํ์คํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ฐํ๋ ๊ฒ๋งํผ ํ๋ฅญํ์ง๋ง Ruby on Rails์ ํฌํจ๋ ๋ฐฐํฐ๋ฆฌ๊ฐ ์ฌ์ ํ ๊ทธ๋ฆฝ์ต๋๋ค. Rails์ ๊ฐ์ ์ฑ์ํ ํ๋ ์์ํฌ๋ฅผ ๊ฑฐ์ 1๋ ์ด ์ง๋์ง ์์ Blitz์ ๋น๊ตํ ์ ์๋ค๋ ๊ฒ์ ์๊ณ ์์ง๋ง "๊ทธ๋งํผ ๋ณด์์ด ์์ด์ผ ํ๋ค"๋ ๋๋์ ํ์คํ ๊ทธ๋ฆฝ์ต๋๋ค.
์น ์ฑ์์ ์์ ํ ๋ ๊ฐ์ฅ ๊ทธ๋ฆฌ์ํ๋ ๊ฒ ์ค ํ๋๋ ๊ถํ ๋ถ์ฌ๋ฅผ ๊ด๋ฆฌํ๋ ์ค์ ์ง์ค์ ์ฅ์์ ๋๋ค. ์น์ธ์ ์น ์ฑ์ด ๊ฐ์ง ์ ์๋ ๊ฐ์ฅ ์ผ๋ฐ์ ์ธ ์๊ตฌ ์ฌํญ์ ๋๋ค. Blitz ์์ฒด๊ฐ ์ธ์ ์ ๋ด์ฅ๋
$authorize
๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๊ณ ์๋ํ์ง๋ง ๊ถํ ๋ถ์ฌ๊ฐ ๋ฐ์ดํฐ ์์ฑ์ ์ข
์๋๋ ๊ฒฝ์ฐ ๋ถ์กฑํฉ๋๋ค. ๊ธฐ์ ์ ์ผ๋ก attributes-based access control .์ข์ ์์์ ์น๊ตฌ์ด์ ์ Ingenious ์ง์์ด ๋ง๋ Blitz Guard API๊ฐ RoR cancancan gem์ ๊ฐ๊น์ง๋ง Blitz์ ํจ๊ป ์๋ํ๋๋ก ์กฐ์ ๋์๋ค๋ ๊ฒ์ ๋๋ค.
์ค์น
Blitz Guard ์ต์ ๋ฆด๋ฆฌ์ค์์๋ ๋ค์์ ์คํํ ์ ์์ต๋๋ค.
$ blitz install ntgussoni/blitz-guard-recipe
์ด ๋ผ์ธ์ Blitz Guard ๋ ์ํผ๋ฅผ ์ฌ์ฉํ์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํฉ๋๋ค. Recipes์ ์ฑ์ ์ ์ํํธ์จ์ด๋ฅผ ์ค์นํ๋ ํ๋ฅญํ ์๋ด ๋ฐฉ๋ฒ์
๋๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ฐ๋ฐ์๊ฐ ์ฝ๋๋ฒ ์ด์ค๋ฅผ ์์์ํค์ง ์๊ณ ์
๋ฐ์ดํธํ ์ ์์ต๋๋ค.
์์ ๋ญ๊ฐ ๋ค์ด์์ด
์ผ๋จ ์ค์นํ๋ฉด ์ฌ๋ฌ ๊ฐ์ ์ ํ์ผ๋ก ๋๋ฉ๋๋ค. ๊ฐ์ฅ ์ค์ํ ๊ฒ์ app/guard/ability.ts
์
๋๋ค. ์ด ํ์ผ์ ๊ถํ ๋ถ์ฌ ๋
ผ๋ฆฌ์ ํต์ฌ์ด๋ฉฐ ์ฌ์ฉ์๊ฐ ์ฑ์์ ์์
์ ์ํํ ์ ์๋์ง ์ฌ๋ถ์ ๊ด๊ณ์์ด ๋จ์ผ ์ ๋ณด ์์ค ์ญํ ์ ํฉ๋๋ค.
import db from "db"
import { GuardBuilder, PrismaModelsType } from "@blitz-guard/core"
import { GetShoppingCartInput } from "app/shoppingCarts/queries/getShoppingCart"
type ExtendedResourceTypes = PrismaModelsType<typeof db>
type ExtendedAbilityTypes = ""
const Guard = GuardBuilder<ExtendedResourceTypes, ExtendedAbilityTypes>(
async (ctx, { can, cannot }) => {
cannot("manage", "all")
if (ctx.session.$isAuthorized()) {
can("read", "shoppingCart", async ({ where }: GetShoppingCartInput) => {
return where.userId === ctx.session.userId
})
}
}
)
export default Guard
ability
ํ์ผ์ Guard
ํจ์๋ฅผ ์ฌ์ฉํ์ฌ GuardBuilder
๋ฅผ ์์ฑํฉ๋๋ค. ๊ฒฐ๊ณผ ๊ฐ๋๋ ๋ชจ๋ ์น์ธ๋ ์ฟผ๋ฆฌ ๋๋ ๋์ฐ๋ณ์ด์ ๋ํด ์คํ๋ฉ๋๋ค.
์๋ฅผ ๋ค์ด ์์ ์๋ฅผ ๊ธฐ์ค์ผ๋ก ์ฅ๋ฐ๊ตฌ๋์ ๋ํ ์ก์ธ์ค๋ฅผ ๊ฑฐ๋ถํ๋ ค๋ ๊ฒฝ์ฐ์
๋๋ค. ๋ค์๊ณผ ๊ฐ์ด ํ ์ ์์ต๋๋ค.
// app/guard/ability.ts
import { GetShoppingCartInput } from "app/shoppingCart/queries/getShoppingCart"
// ...
if (ctx.session.$isAuthorized()) {
can("read", "shoppingCart", async ( { where }: GetShoppingCartInput ) => {
return where.userId === ctx.session.userId;
})
}
GetShoppingCartInput์ getShoppingCart ์ฟผ๋ฆฌ์์ ์ฌ์ฉํ๋ ๊ฒ๊ณผ ๋์ผํ TS ์ ํ์
๋๋ค.
can
(๋ฐ cannot
) ๋ฉ์๋์๋ ์ธ ๊ฐ์ ๋งค๊ฐ ๋ณ์๊ฐ ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ๋ ์ํํ ์์
(์์ฑ, ์ฝ๊ธฐ, ์
๋ฐ์ดํธ, ์ญ์ ๋๋ ๊ด๋ฆฌ)์ด๊ณ , ๋ ๋ฒ์งธ๋ ์ด ๊ฐ๋๊ฐ ์ ์ฉ๋๋ Prisma ์คํค๋ง ๊ฐ์ฒด์
๋๋ค(์ด๋ Prisma์ ์ข
์๋ ํ์๊ฐ ์์ผ๋ฉฐ ๋ค์์ ์ฌ์ฉํ์ฌ ํ์ฅํ ์ ์์ต๋๋ค. ExtendedResourceTypes
), ์ธ ๋ฒ์งธ๋ ๋ถ์ธ๋ก ํด์๋์ด์ผ ํ๋ ๋น๋๊ธฐ ํจ์์
๋๋ค.
์ด ์ธ ๋ฒ์งธ ์ธ์์์๋ ๋ก๊ทธ์ธํ ์ฌ์ฉ์์๊ฒ ์ง์ ๋ ๋ฆฌ์์ค์ ๋ํ ์์
์ ์ํํ ๊ถํ์ด ์๋์ง ์ฌ๋ถ๋ฅผ ํ์ธํ๊ธฐ ์ํด DB ์ฟผ๋ฆฌ์ ๊ฐ์ ์ํ๋ ๋
ผ๋ฆฌ๋ฅผ ๊ตฌํํ ์ ์์ต๋๋ค. ๋ ํฅ๋ฏธ๋ก์ด ์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
//...
if (ctx.session.$isAuthorized()) {
can("read", "shoppingCart", async ( { where }: GetShoppingCartInput ) => {
if(where.userId === ctx.session.userId) return true
const count = await db.shoppingCartShare.count({ where: { userId: ctx.session.userId, shoppingCartId: where.id } })
return count > 0
})
}
โ๏ธ ์ฌ๊ธฐ์ ์ฐ๋ฆฌ๋ ์ฌ์ฉ์๊ฐ ์ฅ๋ฐ๊ตฌ๋ ์์ ์์ด๊ฑฐ๋ ์ฅ๋ฐ๊ตฌ๋๊ฐ ์ด์ ์ ์ด ์ฌ์ฉ์์ ๊ณต์ ๋์๋ค๊ณ ์ฃผ์ฅํฉ๋๋ค.
๊ถํ ๋ถ์ฌ ๊ธฐ๋ฅ
์ง๊ธ๊น์ง๋ ๋๋ฌด ์ข์์ง๋ง ๊ธฐ๋ฅ์ ์น์ธํ์ง ์์ผ๋ฉด ๋ฅ๋ ฅ ํ์ผ์ ๋ณ๊ฒฝํด๋ ์๋ฌด ์์ฉ์ด ์์ต๋๋ค. Blitz Guard๋ authorize
๋ฉ์๋๋ก ์ฟผ๋ฆฌ์ ๋์ฐ๋ณ์ด๋ฅผ ๋ํํ๋ ๊ฒฝ์ฐ์๋ง ํจ์ ํธ์ถ์ ๊ฐ๋ก์ฑ์ ๊ฐ๋๋ฅผ ์คํํฉ๋๋ค.
import { Ctx, NotFoundError } from "blitz"
import db, { Prisma } from "db"
import Guard from "app/guard/ability"
export type GetShoppingCartInput = Pick<Prisma.ShoppingCartFindFirstArgs, "where">
async function getShoppingCart({ where }: GetShoppingCartInput, ctx: Ctx) {
ctx.session.$authorize()
const cart = await db.shoppingCart.findFirst({ where })
if (!cart) throw new NotFoundError()
return cart
}
export default Guard.authorize("read", "shoppingCart", getShoppingCart)
์ด ํจ์์ ์ฒ์ ๋ ์ธ์๋ can
๋ฐ cannot
ํจ์๊ฐ ๋ฐ๋ ๋์ผํ ์ธ์์ด๊ธฐ ๋๋ฌธ์ ์น์ํด ๋ณด์ผ ๊ฒ์
๋๋ค. ์ธ ๋ฒ์งธ ์ธ์๋ ์ฐ๋ฆฌ๊ฐ ๊ฐ์ธ๊ณ ์ ํ๋ ํจ์์ด๋ฉฐ, ๊ฐ๋ ๊ธฐ์ค์ด ์ถฉ์กฑ๋ ๋๋ง ํธ์ถ๋ฉ๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด 403์ด ๋ฉ๋๋ค.
์ด ๋จ๊ณ๋ ์์ด๋ฒ๋ฆฌ๊ธฐ ์ฝ๊ณ , Blitz ์์ฑ๊ธฐ ๊ธฐ๋ณธ๊ฐ์ด ์์ฑ๋ ๊ธฐ๋ฅ์ ๋ด๋ณด๋ผ ๋ ๋์ฑ ๊ทธ๋ ์ต๋๋ค. ์ด๋ฅผ ๋๊ธฐ ์ํด Blitz Guard๋ Guard.authorize
ํจ์๋ก ๋ํ๋์ง ์์ ์ฟผ๋ฆฌ ๋ฐ ๋ณํ์ ๋ํด (๊ฐ๋ฐ ์ค) ๊ฒฝ๊ณ ํ๋ ๋ฏธ๋ค์จ์ด๋ฅผ ์ค์นํฉ๋๋ค.
๋ง์ง๋ง ๋จ์ด
Blitz Guard๋ ์์ง ๋ง์ด ๊ฐ๋ฐ ์ค์ด์ง๋ง API๊ฐ ๋ง์ด ๋ฐ๋ ๊ฒ์ด๋ผ๊ณ ์๊ฐํ์ง ์์ต๋๋ค. ํฉ์ด์ ธ ์๋ ๋น์ฆ๋์ค ๋ก์ง์ ํ ๊ณณ์ผ๋ก ์ฎ๊ฒจ ์ผ์ฌ์ฐฌ Blitz ์ฑ์ ๊ฐ๋ฐํ ๊ณํ์ด๋ผ๋ฉด ํ๋ฅญํ ์ต์
์
๋๋ค.
Reference
์ด ๋ฌธ์ ์ ๊ดํ์ฌ(๐ก ๋ธ๋ฆฌ์ธ ๊ฐ๋), ์ฐ๋ฆฌ๋ ์ด๊ณณ์์ ๋ ๋ง์ ์๋ฃ๋ฅผ ๋ฐ๊ฒฌํ๊ณ ๋งํฌ๋ฅผ ํด๋ฆญํ์ฌ ๋ณด์๋ค
https://dev.to/iamcherta/blitz-guard-936
ํ
์คํธ๋ฅผ ์์ ๋กญ๊ฒ ๊ณต์ ํ๊ฑฐ๋ ๋ณต์ฌํ ์ ์์ต๋๋ค.ํ์ง๋ง ์ด ๋ฌธ์์ URL์ ์ฐธ์กฐ URL๋ก ๋จ๊ฒจ ๋์ญ์์ค.
์ฐ์ํ ๊ฐ๋ฐ์ ์ฝํ
์ธ ๋ฐ๊ฒฌ์ ์ ๋
(Collection and Share based on the CC Protocol.)
$ blitz install ntgussoni/blitz-guard-recipe
์ผ๋จ ์ค์นํ๋ฉด ์ฌ๋ฌ ๊ฐ์ ์ ํ์ผ๋ก ๋๋ฉ๋๋ค. ๊ฐ์ฅ ์ค์ํ ๊ฒ์
app/guard/ability.ts
์
๋๋ค. ์ด ํ์ผ์ ๊ถํ ๋ถ์ฌ ๋
ผ๋ฆฌ์ ํต์ฌ์ด๋ฉฐ ์ฌ์ฉ์๊ฐ ์ฑ์์ ์์
์ ์ํํ ์ ์๋์ง ์ฌ๋ถ์ ๊ด๊ณ์์ด ๋จ์ผ ์ ๋ณด ์์ค ์ญํ ์ ํฉ๋๋ค.import db from "db"
import { GuardBuilder, PrismaModelsType } from "@blitz-guard/core"
import { GetShoppingCartInput } from "app/shoppingCarts/queries/getShoppingCart"
type ExtendedResourceTypes = PrismaModelsType<typeof db>
type ExtendedAbilityTypes = ""
const Guard = GuardBuilder<ExtendedResourceTypes, ExtendedAbilityTypes>(
async (ctx, { can, cannot }) => {
cannot("manage", "all")
if (ctx.session.$isAuthorized()) {
can("read", "shoppingCart", async ({ where }: GetShoppingCartInput) => {
return where.userId === ctx.session.userId
})
}
}
)
export default Guard
ability
ํ์ผ์ Guard
ํจ์๋ฅผ ์ฌ์ฉํ์ฌ GuardBuilder
๋ฅผ ์์ฑํฉ๋๋ค. ๊ฒฐ๊ณผ ๊ฐ๋๋ ๋ชจ๋ ์น์ธ๋ ์ฟผ๋ฆฌ ๋๋ ๋์ฐ๋ณ์ด์ ๋ํด ์คํ๋ฉ๋๋ค.์๋ฅผ ๋ค์ด ์์ ์๋ฅผ ๊ธฐ์ค์ผ๋ก ์ฅ๋ฐ๊ตฌ๋์ ๋ํ ์ก์ธ์ค๋ฅผ ๊ฑฐ๋ถํ๋ ค๋ ๊ฒฝ์ฐ์ ๋๋ค. ๋ค์๊ณผ ๊ฐ์ด ํ ์ ์์ต๋๋ค.
// app/guard/ability.ts
import { GetShoppingCartInput } from "app/shoppingCart/queries/getShoppingCart"
// ...
if (ctx.session.$isAuthorized()) {
can("read", "shoppingCart", async ( { where }: GetShoppingCartInput ) => {
return where.userId === ctx.session.userId;
})
}
GetShoppingCartInput์ getShoppingCart ์ฟผ๋ฆฌ์์ ์ฌ์ฉํ๋ ๊ฒ๊ณผ ๋์ผํ TS ์ ํ์ ๋๋ค.
can
(๋ฐ cannot
) ๋ฉ์๋์๋ ์ธ ๊ฐ์ ๋งค๊ฐ ๋ณ์๊ฐ ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ๋ ์ํํ ์์
(์์ฑ, ์ฝ๊ธฐ, ์
๋ฐ์ดํธ, ์ญ์ ๋๋ ๊ด๋ฆฌ)์ด๊ณ , ๋ ๋ฒ์งธ๋ ์ด ๊ฐ๋๊ฐ ์ ์ฉ๋๋ Prisma ์คํค๋ง ๊ฐ์ฒด์
๋๋ค(์ด๋ Prisma์ ์ข
์๋ ํ์๊ฐ ์์ผ๋ฉฐ ๋ค์์ ์ฌ์ฉํ์ฌ ํ์ฅํ ์ ์์ต๋๋ค. ExtendedResourceTypes
), ์ธ ๋ฒ์งธ๋ ๋ถ์ธ๋ก ํด์๋์ด์ผ ํ๋ ๋น๋๊ธฐ ํจ์์
๋๋ค.์ด ์ธ ๋ฒ์งธ ์ธ์์์๋ ๋ก๊ทธ์ธํ ์ฌ์ฉ์์๊ฒ ์ง์ ๋ ๋ฆฌ์์ค์ ๋ํ ์์ ์ ์ํํ ๊ถํ์ด ์๋์ง ์ฌ๋ถ๋ฅผ ํ์ธํ๊ธฐ ์ํด DB ์ฟผ๋ฆฌ์ ๊ฐ์ ์ํ๋ ๋ ผ๋ฆฌ๋ฅผ ๊ตฌํํ ์ ์์ต๋๋ค. ๋ ํฅ๋ฏธ๋ก์ด ์๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
//...
if (ctx.session.$isAuthorized()) {
can("read", "shoppingCart", async ( { where }: GetShoppingCartInput ) => {
if(where.userId === ctx.session.userId) return true
const count = await db.shoppingCartShare.count({ where: { userId: ctx.session.userId, shoppingCartId: where.id } })
return count > 0
})
}
โ๏ธ ์ฌ๊ธฐ์ ์ฐ๋ฆฌ๋ ์ฌ์ฉ์๊ฐ ์ฅ๋ฐ๊ตฌ๋ ์์ ์์ด๊ฑฐ๋ ์ฅ๋ฐ๊ตฌ๋๊ฐ ์ด์ ์ ์ด ์ฌ์ฉ์์ ๊ณต์ ๋์๋ค๊ณ ์ฃผ์ฅํฉ๋๋ค.
๊ถํ ๋ถ์ฌ ๊ธฐ๋ฅ
์ง๊ธ๊น์ง๋ ๋๋ฌด ์ข์์ง๋ง ๊ธฐ๋ฅ์ ์น์ธํ์ง ์์ผ๋ฉด ๋ฅ๋ ฅ ํ์ผ์ ๋ณ๊ฒฝํด๋ ์๋ฌด ์์ฉ์ด ์์ต๋๋ค. Blitz Guard๋ authorize
๋ฉ์๋๋ก ์ฟผ๋ฆฌ์ ๋์ฐ๋ณ์ด๋ฅผ ๋ํํ๋ ๊ฒฝ์ฐ์๋ง ํจ์ ํธ์ถ์ ๊ฐ๋ก์ฑ์ ๊ฐ๋๋ฅผ ์คํํฉ๋๋ค.
import { Ctx, NotFoundError } from "blitz"
import db, { Prisma } from "db"
import Guard from "app/guard/ability"
export type GetShoppingCartInput = Pick<Prisma.ShoppingCartFindFirstArgs, "where">
async function getShoppingCart({ where }: GetShoppingCartInput, ctx: Ctx) {
ctx.session.$authorize()
const cart = await db.shoppingCart.findFirst({ where })
if (!cart) throw new NotFoundError()
return cart
}
export default Guard.authorize("read", "shoppingCart", getShoppingCart)
์ด ํจ์์ ์ฒ์ ๋ ์ธ์๋ can
๋ฐ cannot
ํจ์๊ฐ ๋ฐ๋ ๋์ผํ ์ธ์์ด๊ธฐ ๋๋ฌธ์ ์น์ํด ๋ณด์ผ ๊ฒ์
๋๋ค. ์ธ ๋ฒ์งธ ์ธ์๋ ์ฐ๋ฆฌ๊ฐ ๊ฐ์ธ๊ณ ์ ํ๋ ํจ์์ด๋ฉฐ, ๊ฐ๋ ๊ธฐ์ค์ด ์ถฉ์กฑ๋ ๋๋ง ํธ์ถ๋ฉ๋๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด 403์ด ๋ฉ๋๋ค.
์ด ๋จ๊ณ๋ ์์ด๋ฒ๋ฆฌ๊ธฐ ์ฝ๊ณ , Blitz ์์ฑ๊ธฐ ๊ธฐ๋ณธ๊ฐ์ด ์์ฑ๋ ๊ธฐ๋ฅ์ ๋ด๋ณด๋ผ ๋ ๋์ฑ ๊ทธ๋ ์ต๋๋ค. ์ด๋ฅผ ๋๊ธฐ ์ํด Blitz Guard๋ Guard.authorize
ํจ์๋ก ๋ํ๋์ง ์์ ์ฟผ๋ฆฌ ๋ฐ ๋ณํ์ ๋ํด (๊ฐ๋ฐ ์ค) ๊ฒฝ๊ณ ํ๋ ๋ฏธ๋ค์จ์ด๋ฅผ ์ค์นํฉ๋๋ค.
๋ง์ง๋ง ๋จ์ด
Blitz Guard๋ ์์ง ๋ง์ด ๊ฐ๋ฐ ์ค์ด์ง๋ง API๊ฐ ๋ง์ด ๋ฐ๋ ๊ฒ์ด๋ผ๊ณ ์๊ฐํ์ง ์์ต๋๋ค. ํฉ์ด์ ธ ์๋ ๋น์ฆ๋์ค ๋ก์ง์ ํ ๊ณณ์ผ๋ก ์ฎ๊ฒจ ์ผ์ฌ์ฐฌ Blitz ์ฑ์ ๊ฐ๋ฐํ ๊ณํ์ด๋ผ๋ฉด ํ๋ฅญํ ์ต์
์
๋๋ค.
Reference
์ด ๋ฌธ์ ์ ๊ดํ์ฌ(๐ก ๋ธ๋ฆฌ์ธ ๊ฐ๋), ์ฐ๋ฆฌ๋ ์ด๊ณณ์์ ๋ ๋ง์ ์๋ฃ๋ฅผ ๋ฐ๊ฒฌํ๊ณ ๋งํฌ๋ฅผ ํด๋ฆญํ์ฌ ๋ณด์๋ค
https://dev.to/iamcherta/blitz-guard-936
ํ
์คํธ๋ฅผ ์์ ๋กญ๊ฒ ๊ณต์ ํ๊ฑฐ๋ ๋ณต์ฌํ ์ ์์ต๋๋ค.ํ์ง๋ง ์ด ๋ฌธ์์ URL์ ์ฐธ์กฐ URL๋ก ๋จ๊ฒจ ๋์ญ์์ค.
์ฐ์ํ ๊ฐ๋ฐ์ ์ฝํ
์ธ ๋ฐ๊ฒฌ์ ์ ๋
(Collection and Share based on the CC Protocol.)
import { Ctx, NotFoundError } from "blitz"
import db, { Prisma } from "db"
import Guard from "app/guard/ability"
export type GetShoppingCartInput = Pick<Prisma.ShoppingCartFindFirstArgs, "where">
async function getShoppingCart({ where }: GetShoppingCartInput, ctx: Ctx) {
ctx.session.$authorize()
const cart = await db.shoppingCart.findFirst({ where })
if (!cart) throw new NotFoundError()
return cart
}
export default Guard.authorize("read", "shoppingCart", getShoppingCart)
Blitz Guard๋ ์์ง ๋ง์ด ๊ฐ๋ฐ ์ค์ด์ง๋ง API๊ฐ ๋ง์ด ๋ฐ๋ ๊ฒ์ด๋ผ๊ณ ์๊ฐํ์ง ์์ต๋๋ค. ํฉ์ด์ ธ ์๋ ๋น์ฆ๋์ค ๋ก์ง์ ํ ๊ณณ์ผ๋ก ์ฎ๊ฒจ ์ผ์ฌ์ฐฌ Blitz ์ฑ์ ๊ฐ๋ฐํ ๊ณํ์ด๋ผ๋ฉด ํ๋ฅญํ ์ต์ ์ ๋๋ค.
Reference
์ด ๋ฌธ์ ์ ๊ดํ์ฌ(๐ก ๋ธ๋ฆฌ์ธ ๊ฐ๋), ์ฐ๋ฆฌ๋ ์ด๊ณณ์์ ๋ ๋ง์ ์๋ฃ๋ฅผ ๋ฐ๊ฒฌํ๊ณ ๋งํฌ๋ฅผ ํด๋ฆญํ์ฌ ๋ณด์๋ค https://dev.to/iamcherta/blitz-guard-936ํ ์คํธ๋ฅผ ์์ ๋กญ๊ฒ ๊ณต์ ํ๊ฑฐ๋ ๋ณต์ฌํ ์ ์์ต๋๋ค.ํ์ง๋ง ์ด ๋ฌธ์์ URL์ ์ฐธ์กฐ URL๋ก ๋จ๊ฒจ ๋์ญ์์ค.
์ฐ์ํ ๊ฐ๋ฐ์ ์ฝํ ์ธ ๋ฐ๊ฒฌ์ ์ ๋ (Collection and Share based on the CC Protocol.)
์ข์ ์นํ์ด์ง ์ฆ๊ฒจ์ฐพ๊ธฐ
๊ฐ๋ฐ์ ์ฐ์ ์ฌ์ดํธ ์์ง
๊ฐ๋ฐ์๊ฐ ์์์ผ ํ ํ์ ์ฌ์ดํธ 100์ ์ถ์ฒ ์ฐ๋ฆฌ๋ ๋น์ ์ ์ํด 100๊ฐ์ ์์ฃผ ์ฌ์ฉํ๋ ๊ฐ๋ฐ์ ํ์ต ์ฌ์ดํธ๋ฅผ ์ ๋ฆฌํ์ต๋๋ค