Skip to content
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ yarn-error.log*

# turbo
.turbo

# history
.history
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ index.js
# Build
packages/**/lib
apps/**/.next

# history
.history
4 changes: 4 additions & 0 deletions apps/web-main/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@
"type-check": "tsc --noEmit"
},
"dependencies": {
"@condog-pkg/utils": "*",
"cors": "^2.8.5",
"line-login": "^1.0.14",
"next": "13.0.3",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"@condog-pkg/eslint-config": "*",
"@condog-pkg/tsconfig": "*",
"@types/cors": "^2.8.13",
"@types/node": "18.11.9",
"@types/react": "18.0.25",
"@types/react-dom": "18.0.9",
Expand Down
37 changes: 37 additions & 0 deletions apps/web-main/src/app/home/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { use, useCallback } from 'react';
import { lineAuthSdk } from '../../lib/LineAuthSdk';

type User = {
id: number;
login: string;
};

const fetchUsers: () => Promise<User[]> = async () => {
const res = await fetch('https://api.github.com/users');
return res.json();
};

export default function Page() {
const users = use(fetchUsers());
console.log(users);

const handleButtonClick = useCallback(() => {
lineAuthSdk.signout();
}, []);

return (
<div>
<h1>GitHub Users!</h1>
<div>
{users.map((user) => {
return (
<div key={user.id}>
{user.id}: {user.login}
</div>
);
})}
</div>
<button onClick={handleButtonClick}>signout</button>
</div>
);
}
51 changes: 30 additions & 21 deletions apps/web-main/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,39 @@
import { use } from 'react';
'use client';

type User = {
id: number;
login: string;
};

const fetchUsers: () => Promise<User[]> = async () => {
const res = await fetch('https://api.github.com/users');
return res.json();
};
import Link from 'next/link';
// import Link from 'next/link';
import qs from 'qs';
import { useMemo } from 'react';

export default function Page() {
const users = use(fetchUsers());
console.log(users);
const navigateLoginUrl = useMemo(() => {
const query = qs.stringify({
response_type: 'code',
client_id: '1657782125',
redirect_uri: 'http://localhost:3000/api/line-auth/callback',
state: 'hoge', // TODO: must generate random string
scope: 'profile',
botPrompt: 'aggressive',
});
const returnUri = `https://access.line.me/oauth2/v2.1/authorize?${query}`;
const loginChannelId = '1657782125';
const loginState = 'hoge'; // TODO: must generate random string

return `https://access.line.me/oauth2/v2.1/login?returnUri=${encodeURIComponent(
returnUri
)}&loginChannelId=${loginChannelId}&loginState=${loginState}`;
}, []);

return (
<div>
<h1>GitHub Users!</h1>
<div>
{users.map((user) => {
return (
<div key={user.id}>
{user.id}: {user.login}
</div>
);
})}
</div>
{/* <div onClick={navigateLogin}>XXX</div> */}
<Link href={navigateLoginUrl}>Sign in</Link>
</div>
);
}

// /oauth2/v2.1/authorize/consent?response_type=code&client_id=1655087825&redirect_uri=https%3A%2F%2Fmarouge.jp%2Fc%2Fregister%2Fline%3FredirectUrl%3Dhttps%3A%2F%2Fmarouge.jp%2F&state=238568bde56a4567a730927b5d2e2e0f&scope=profile+openid+email&bot_prompt=aggressive
// /oauth2/v2.1/authorize?response_type=code&client_id=1655087825&redirect_uri=https://marouge.jp/c/register/line?redirectUrl=https://marouge.jp/&state=238568bde56a4567a730927b5d2e2e0f&scope=profile openid email&bot_prompt=aggressive
// /oauth2/v2.1/login?returnUri=/oauth2/v2.1/authorize/consent?response_type=code&client_id=1655087825&redirect_uri=https://marouge.jp/c/register/line?redirectUrl=https://marouge.jp/&state=238568bde56a4567a730927b5d2e2e0f&scope=profile openid email&bot_prompt=aggressive&loginChannelId=1655087825&loginState=Jd6gxNI7aquydGBIfc7LSe
// /oauth2/v2.1/login?returnUri=%2Foauth2%2Fv2.1%2Fauthorize%2Fconsent%3Fresponse_type%3Dcode%26client_id%3D1655087825%26redirect_uri%3Dhttps%253A%252F%252Fmarouge.jp%252Fc%252Fregister%252Fline%253FredirectUrl%253Dhttps%253A%252F%252Fmarouge.jp%252F%26state%3D238568bde56a4567a730927b5d2e2e0f%26scope%3Dprofile%2Bopenid%2Bemail%26bot_prompt%3Daggressive&loginChannelId=1655087825&loginState=Jd6gxNI7aquydGBIfc7LSe
31 changes: 31 additions & 0 deletions apps/web-main/src/lib/LineAuthSdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export class LineAuthSdk {
basePath: string;

constructor(basePath: string) {
this.basePath = basePath;
}

signin = async () => {
try {
const res = await fetch(`${this.basePath}/signin`, {
method: 'POST',
body: JSON.stringify({}),
});
console.log(JSON.stringify(res), 'XXX');
console.log(res);
console.log(await res.json(), 'YYY');
return res;
} catch (e) {
console.error(e);
console.log(e);
console.log(JSON.stringify(e), 'ZZZ');
}
};
signout = async () => {
const res = await fetch(`${this.basePath}/signout`);
console.log(res);
return res;
};
}

export const lineAuthSdk = new LineAuthSdk('/api/line-auth');
89 changes: 89 additions & 0 deletions apps/web-main/src/lib/coockie.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type { NextApiResponse } from 'next';

export const CookieNames = {
AUTH_TOKEN: 'auth_token',
CLIENT_ID: 'client_id',
SESSION_ID: 'session_id',
} as const;

export type CookieName = typeof CookieNames[keyof typeof CookieNames];

export type CookieSetting = {
name: CookieName;
expires: number;
maxAge?: number;
path: string;
secure: boolean;
httpOnly: boolean;
sameSite: 'strict' | 'lax' | 'none';
};

const getCookieSetting = (name: CookieName) => {
const cookieSettings: Record<CookieName, CookieSetting> = {
[CookieNames.AUTH_TOKEN]: {
name: CookieNames.AUTH_TOKEN,
expires: 60 * 60 * 24 * 30 * 1000,
path: '/',
secure: true,
httpOnly: false,
sameSite: 'lax',
},
[CookieNames.CLIENT_ID]: {
name: CookieNames.CLIENT_ID,
expires: 60 * 60 * 24 * 30 * 1000,
path: '/',
secure: true,
httpOnly: false,
sameSite: 'lax',
},
[CookieNames.SESSION_ID]: {
name: CookieNames.SESSION_ID,
expires: 60 * 60 * 1000,
maxAge: 60 * 60,
path: '/',
secure: true,
httpOnly: true,
sameSite: 'lax',
},
};

return cookieSettings[name];
};

export const set = (
res: NextApiResponse,
name: CookieName,
{
value = '',
now = Date.now(),
domain,
remove,
}: { value?: string; now?: number; domain?: string; remove?: true } = {}
) => {
const setting = getCookieSetting(name);

const expires = new Date(now + setting.expires);
const values = [
`${setting.name}=${value}`,
`path=${setting.path}`,
`expires=${expires.toUTCString()}`,
`samesite=${setting.sameSite}`,
];

domain && values.push(`domain=${domain}`);
setting.httpOnly && values.push('httponly');
setting.secure && values.push('secure');
if (!remove) {
setting.maxAge && values.push(`max-age=${setting.maxAge}`);
}
remove && values.push('max-age=0');
res.setHeader('set-cookie', values.join(';'));
};

export const remove = (
res: NextApiResponse,
name: CookieName,
{ domain }: { domain?: string } = {}
) => {
set(res, name, { domain, remove: true });
};
24 changes: 24 additions & 0 deletions apps/web-main/src/lib/passport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { LineStrategy } from '@condog-pkg/utils';

export const lineStrategy = new LineStrategy(
{
channelID: 'XXX',
channelSecret: 'XXX',
callbackURL: 'localhost:3000/api/line-auth/callback',
scope: ['profile', 'openid', 'email'],
botPrompt: 'normal',
prompt: 'consent',
customHeaders: {
accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'accept-encoding': 'gzip, deflate, br',
'accecpt-language': 'ja-JP,ja;q=0.9,en-US;q=0.8,en;q=0.7',
},
},
(accessToken, refreshToken, profile, done) => {
console.log('accessToken', accessToken);
console.log('refreshToken', refreshToken);
console.log('profile', profile);
done(null, profile);
}
);
27 changes: 27 additions & 0 deletions apps/web-main/src/pages/api/line-auth/callback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import nextConnect from 'next-connect';
import passport from 'passport';
import { CookieNames, set } from '../../../lib/coockie';
import { lineStrategy } from '../../../lib/passport';

passport.use('line', lineStrategy);

export default nextConnect<NextApiRequest, NextApiResponse>()
.use(passport.initialize())
.get(async (req, res) => {
const authorizeFn = passport.authorize(
'line',
{
failureRedirect: '/',
},
(req, res) => {
console.log('req.body', req.body);
set(res, CookieNames.AUTH_TOKEN, { value: JSON.stringify(req.body) });
res.statusCode = 200;
res.json({ done: true });
}
);

console.log(authorizeFn);
authorizeFn(req, res);
});
42 changes: 42 additions & 0 deletions apps/web-main/src/pages/api/line-auth/signin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import nextConnect from 'next-connect';
import passport from 'passport';
import { CookieNames, set } from '../../../lib/coockie';
import qs from 'querystring';
import { lineStrategy } from '../../../lib/passport';

passport.use('line', lineStrategy);

// export default nextConnect<NextApiRequest, NextApiResponse>({
// onError(error, req, res) {
// res.status(501).json({
// error: `Sorry something Happened! ${error.message}`,
// req: JSON.stringify(req.body),
// res: JSON.stringify(res),
// });
// },
// })
// .use(passport.initialize())
// .get((req, res, ...args) => {
// const fn = passport.authenticate('line');
// return fn(req, res, ...args);
// });

export default nextConnect<NextApiRequest, NextApiResponse>().get((_, res) => {
const query = qs.stringify({
response_type: 'code',
client_id: 'XXX',
redirect_uri: 'http://localhost:3000/api/line-auth/callback',
state: 'hoge', // TODO: must generate random string
scope: 'profile',
botPrompt: 'aggressive',
});
const returnUri = `https://access.line.me/oauth2/v2.1/authorize?${query}`;
const loginChannelId = 'XXX';
const loginState = 'hoge'; // TODO: must generate random string

const loginUrl = `https://access.line.me/oauth2/v2.1/login?returnUri=${encodeURIComponent(
returnUri
)}&loginChannelId=${loginChannelId}&loginState=${loginState}`;
res.redirect(loginUrl);
});
11 changes: 11 additions & 0 deletions apps/web-main/src/pages/api/line-auth/signout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import nextConnect from 'next-connect';
import { CookieNames, remove } from '../../../lib/coockie';

export default nextConnect<NextApiRequest, NextApiResponse>().post(
async (_, res) => {
remove(res, CookieNames.AUTH_TOKEN);
res.writeHead(302, { Location: '/' });
res.end();
}
);
Loading