Skip to content

Commit

Permalink
Merge branch 'main' into add-instruction-property
Browse files Browse the repository at this point in the history
  • Loading branch information
toyamarinyon committed Nov 29, 2024
2 parents 379abad + 1608194 commit 2d50bcd
Show file tree
Hide file tree
Showing 43 changed files with 4,615 additions and 143 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/create_issue_security_txt.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Create an issue to remind updating of security.txt

on:
workflow_dispatch:
schedule:
- cron: "0 0 1 6,12 *" # Every year on 6/1 and 12/1 at 9:00am JST

jobs:
get_date:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Get this month
id: get_this_month
run: |
echo "this_month=$(date +%Y/%m)" >> "$GITHUB_OUTPUT"
outputs:
this_month: ${{ steps.get_this_month.outputs.this_month }}
create_issue:
needs: get_date
uses: route06/actions/.github/workflows/create_gh_issue.yml@v2
permissions:
contents: read
issues: write
with:
title: "[Action Required] Update security.txt - ${{ needs.get_date.outputs.this_month }} Maintenance"
description_template_path: .github/workflows/templates/create_issue_security_txt.md
14 changes: 14 additions & 0 deletions .github/workflows/templates/create_issue_security_txt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## Summary

This issue reminds you to update your `security.txt` file to comply with the latest standards and maintain its relevance.

🔗 [RFC 9116: A File Format to Aid in Security Vulnerability Disclosure](https://www.rfc-editor.org/rfc/rfc9116)

> The "Expires" field indicates the date and time after which the data contained in the "security.txt" file is considered stale and should not be used (as per Section 5.3). (snip) It is RECOMMENDED that the value of this field be less than a year into the future to avoid staleness.
## What to do

Please create a pull request to address the following tasks:

* Update the `Expires` field in `security.txt` to a date less than a year in the future
* Review and update any other fields in the file to ensure they remain accurate and relevant
21 changes: 16 additions & 5 deletions app/(auth)/actions.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
"use server";

import { getAuthCallbackUrl } from "@/app/(auth)/lib";
import { type Provider, getAuthCallbackUrl } from "@/app/(auth)/lib";
import { logger } from "@/lib/logger";
import { createClient } from "@/lib/supabase";
import { redirect } from "next/navigation";

export async function authorizeGitHub() {
async function authorizeOAuth(provider: Provider) {
const supabase = await createClient();

const { data, error } = await supabase.auth.signInWithOAuth({
provider: "github",
provider,
options: {
redirectTo: getAuthCallbackUrl(),
redirectTo: getAuthCallbackUrl({ provider }),
},
});
logger.debug(`authorized with ${provider}`);

if (error != null) {
const { code, message, name, status } = error;
throw new Error(`${name} occurred: ${code} (${status}): ${message}`);
}
logger.debug({ data: data }, `OAuth data got from ${provider}`);

if (data.url) {
redirect(data.url);
}
}

export async function authorizeGitHub() {
return authorizeOAuth("github");
}

export async function authorizeGoogle() {
return authorizeOAuth("google");
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
// The client you created from the Server-Side Auth instructions
import { db, oauthCredentials, supabaseUserMappings, users } from "@/drizzle";
import { logger } from "@/lib/logger";
import { createClient } from "@/lib/supabase";
import { initializeAccount } from "@/services/accounts";
import type { Session, User } from "@supabase/supabase-js";
import { eq } from "drizzle-orm";
import { NextResponse } from "next/server";
import { type NextRequest, NextResponse } from "next/server";
import type { Provider } from "../../../lib/";

export async function GET(request: Request) {
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ provider: Provider }> },
) {
const { searchParams, origin } = new URL(request.url);
const { provider } = await params;

logger.debug(
{
searchParams,
origin,
url: request.url,
},
"'searchParams' and 'origin' got from request",
);
const errorMessage = checkError(searchParams);
if (errorMessage) {
return new Response(errorMessage, {
Expand All @@ -16,6 +31,7 @@ export async function GET(request: Request) {
}

const code = searchParams.get("code");
logger.debug({ code }, "code got from query param");
// if "next" is in param, use it as the redirect URL
const next = searchParams.get("next") ?? "/";
if (!code) {
Expand All @@ -31,10 +47,17 @@ export async function GET(request: Request) {
});
}

logger.debug(
{
provider: data.session.user.app_metadata.provider,
providers: data.session.user.app_metadata.providers,
},
"session data got from Supabase",
);
try {
const { user, session } = data;
await initializeUserIfNeeded(user);
await storeProviderTokens(user, session);
await storeProviderTokens(user, session, provider);
} catch (error) {
if (error instanceof Error) {
return new Response(error.message, { status: 500 });
Expand Down Expand Up @@ -73,25 +96,22 @@ async function initializeUserIfNeeded(user: User) {
}

// store accessToken and refreshToken
async function storeProviderTokens(user: User, session: Session) {
async function storeProviderTokens(
user: User,
session: Session,
provider: string,
) {
const { provider_token, provider_refresh_token } = session;
if (!provider_token) {
throw new Error("No provider token found");
}

let provider = "";
// https://docs.github.com/ja/authentication/keeping-your-account-and-data-secure/about-authentication-to-github#github-%E3%81%AE%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%83%95%E3%82%A9%E3%83%BC%E3%83%9E%E3%83%83%E3%83%88
if (provider_token.startsWith("ghu_")) {
provider = "github";
}
// TODO: add another logic for other providers
if (provider === "") {
throw new Error("No provider found");
}
logger.debug(`provider: '${provider}'`);

const identity = user.identities?.find((identity) => {
return identity.provider === provider;
});
logger.debug({ currentProvider: provider });
if (!identity) {
throw new Error(`No identity found for provider: ${provider}`);
}
Expand Down
56 changes: 30 additions & 26 deletions app/(auth)/components/oauth-providers.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,38 @@
import { Button } from "@/components/ui/button";
import { SiGithub } from "@icons-pack/react-simple-icons";
import { googleOauthFlag } from "@/flags";
import { SiGithub, SiGoogle } from "@icons-pack/react-simple-icons";
import type { FC } from "react";
import { authorizeGitHub } from "../actions";
import { authorizeGitHub, authorizeGoogle } from "../actions";

type OauthProvidersProps = {
labelPrefix: string;
};

export const OAuthProviders: FC<OauthProvidersProps> = ({ labelPrefix }) => (
<div className="space-y-2">
{/* <Button asChild variant="link">
<Link href="/signup/google">
<SiGoogle className="h-[20px] w-[20px]" />
<p>Sign up with Google</p>
</Link>
</Button> */}
{/**<button
className="w-full flex items-center justify-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-gray-700 hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-cyan-500"
type="button"
>
<Microsoft className="h-5 w-5 mr-2" /> Sign up with Microsoft
</button>**/}
export const OAuthProviders: FC<OauthProvidersProps> = async ({
labelPrefix,
}) => {
const displayGoogleOauth = await googleOauthFlag();

<Button asChild variant="link">
<form>
<SiGithub className="h-[20px] w-[20px]" />
<button type="submit" formAction={authorizeGitHub}>
{labelPrefix} with GitHub
</button>
</form>
</Button>
</div>
);
return (
<div className="space-y-2">
{displayGoogleOauth && (
<Button asChild variant="link">
<form>
<SiGoogle className="h-[20px] w-[20px]" />
<button type="submit" formAction={authorizeGoogle}>
{labelPrefix} with Google
</button>
</form>
</Button>
)}
<Button asChild variant="link">
<form>
<SiGithub className="h-[20px] w-[20px]" />
<button type="submit" formAction={authorizeGitHub}>
{labelPrefix} with GitHub
</button>
</form>
</Button>
</div>
);
};
12 changes: 10 additions & 2 deletions app/(auth)/lib/get-auth-callback-url.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
// https://supabase.com/docs/guides/auth/redirect-urls
export function getAuthCallbackUrl({ next = "/" } = {}): string {
import type { Provider } from "./types";

export function getAuthCallbackUrl({
next = "/",
provider,
}: { next?: string; provider: Provider }): string {
if (!provider) {
throw new Error("Provider is required");
}
let url =
process.env.NEXT_PUBLIC_SITE_URL ??
process.env.NEXT_PUBLIC_VERCEL_URL ??
"http://localhost:3000/";
url = url.startsWith("http") ? url : `https://${url}`;
url = url.endsWith("/") ? url : `${url}/`;
return `${url}auth/callback?next=${encodeURIComponent(next)}`;
return `${url}auth/callback/${provider}?next=${encodeURIComponent(next)}`;
}
15 changes: 8 additions & 7 deletions app/(auth)/lib/get-current-subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import {
db,
organizations,
subscriptions,
supabaseUserMappings,
teamMemberships,
teams,
Expand All @@ -12,17 +12,18 @@ import { eq } from "drizzle-orm";

export const getUserSubscriptionId = async () => {
const user = await getUser();
// TODO: When team plans are released, a user may belong to multiple teams, so we need to handle that case.
// e.g., fetch team id through agents or so.
const [subscription] = await db
.select({
organizationId: organizations.dbId, // todo: replace with 'subscriptionId' if subscriptions table is created in the future
})
.from(organizations)
.innerJoin(teams, eq(teams.organizationDbId, organizations.dbId))
.select({ id: subscriptions.dbId })
.from(subscriptions)
.innerJoin(teams, eq(teams.dbId, subscriptions.teamDbId))
.innerJoin(teamMemberships, eq(teamMemberships.teamDbId, teams.dbId))
.innerJoin(
supabaseUserMappings,
eq(supabaseUserMappings.userDbId, teamMemberships.userDbId),
)
.where(eq(supabaseUserMappings.supabaseUserId, user.id));
return subscription.organizationId;

return subscription?.id ?? "sub_hotfix";
};
3 changes: 1 addition & 2 deletions app/(auth)/lib/get-oauth-credential.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { db, oauthCredentials, supabaseUserMappings, users } from "@/drizzle";
import { getUser } from "@/lib/supabase";
import { and, eq } from "drizzle-orm";

type Provider = "github";
import type { Provider } from "./types";

export async function getOauthCredential(provider: Provider) {
const supabaseUser = await getUser();
Expand Down
1 change: 1 addition & 0 deletions app/(auth)/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export { getCurrentTeam } from "./get-current-team";
export { getOauthCredential } from "./get-oauth-credential";
export { isRoute06User } from "./is-route06-user";
export { refreshOauthCredential } from "./refresh-oauth-credential";
export type { Provider } from "./types";
1 change: 1 addition & 0 deletions app/(auth)/lib/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type Provider = "github" | "google";
Loading

0 comments on commit 2d50bcd

Please sign in to comment.