@meridianjs/google-oauth 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +33 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +127 -0
- package/dist/index.mjs +100 -0
- package/package.json +41 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ModuleDefinition } from '@meridianjs/types';
|
|
2
|
+
|
|
3
|
+
interface GoogleOAuthOptions {
|
|
4
|
+
clientId: string;
|
|
5
|
+
clientSecret: string;
|
|
6
|
+
callbackUrl: string;
|
|
7
|
+
frontendUrl: string;
|
|
8
|
+
}
|
|
9
|
+
interface GoogleProfile {
|
|
10
|
+
googleId: string;
|
|
11
|
+
email: string;
|
|
12
|
+
firstName: string | null;
|
|
13
|
+
lastName: string | null;
|
|
14
|
+
picture: string | null;
|
|
15
|
+
}
|
|
16
|
+
declare class GoogleOAuthService {
|
|
17
|
+
private readonly options;
|
|
18
|
+
private readonly jwks;
|
|
19
|
+
constructor(container: any);
|
|
20
|
+
/** Build the Google OAuth authorization URL with the given state parameter. */
|
|
21
|
+
getAuthUrl(state: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Exchange an authorization code for a GoogleProfile.
|
|
24
|
+
* Verifies the id_token signature against Google's JWK keyset per
|
|
25
|
+
* https://developers.google.com/identity/openid-connect/openid-connect#validatinganidtoken
|
|
26
|
+
*/
|
|
27
|
+
exchangeCode(code: string): Promise<GoogleProfile>;
|
|
28
|
+
get frontendUrl(): string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare const GoogleOAuthModule: ModuleDefinition;
|
|
32
|
+
|
|
33
|
+
export { type GoogleOAuthOptions, GoogleOAuthService, type GoogleProfile, GoogleOAuthModule as default };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ModuleDefinition } from '@meridianjs/types';
|
|
2
|
+
|
|
3
|
+
interface GoogleOAuthOptions {
|
|
4
|
+
clientId: string;
|
|
5
|
+
clientSecret: string;
|
|
6
|
+
callbackUrl: string;
|
|
7
|
+
frontendUrl: string;
|
|
8
|
+
}
|
|
9
|
+
interface GoogleProfile {
|
|
10
|
+
googleId: string;
|
|
11
|
+
email: string;
|
|
12
|
+
firstName: string | null;
|
|
13
|
+
lastName: string | null;
|
|
14
|
+
picture: string | null;
|
|
15
|
+
}
|
|
16
|
+
declare class GoogleOAuthService {
|
|
17
|
+
private readonly options;
|
|
18
|
+
private readonly jwks;
|
|
19
|
+
constructor(container: any);
|
|
20
|
+
/** Build the Google OAuth authorization URL with the given state parameter. */
|
|
21
|
+
getAuthUrl(state: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Exchange an authorization code for a GoogleProfile.
|
|
24
|
+
* Verifies the id_token signature against Google's JWK keyset per
|
|
25
|
+
* https://developers.google.com/identity/openid-connect/openid-connect#validatinganidtoken
|
|
26
|
+
*/
|
|
27
|
+
exchangeCode(code: string): Promise<GoogleProfile>;
|
|
28
|
+
get frontendUrl(): string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
declare const GoogleOAuthModule: ModuleDefinition;
|
|
32
|
+
|
|
33
|
+
export { type GoogleOAuthOptions, GoogleOAuthService, type GoogleProfile, GoogleOAuthModule as default };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
GoogleOAuthService: () => GoogleOAuthService,
|
|
24
|
+
default: () => index_default
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/service.ts
|
|
29
|
+
var import_jose = require("jose");
|
|
30
|
+
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
31
|
+
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
32
|
+
var GOOGLE_JWKS_URL = "https://www.googleapis.com/oauth2/v3/certs";
|
|
33
|
+
var GOOGLE_ISSUERS = ["https://accounts.google.com", "accounts.google.com"];
|
|
34
|
+
var GoogleOAuthService = class {
|
|
35
|
+
options;
|
|
36
|
+
jwks;
|
|
37
|
+
constructor(container) {
|
|
38
|
+
this.options = container.resolve("moduleOptions");
|
|
39
|
+
try {
|
|
40
|
+
const parsed = new URL(this.options.frontendUrl);
|
|
41
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
42
|
+
throw new Error("must be http or https");
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`[google-oauth] Invalid frontendUrl: "${this.options.frontendUrl}". Must be a valid http:// or https:// URL.`
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
this.jwks = (0, import_jose.createRemoteJWKSet)(new URL(GOOGLE_JWKS_URL));
|
|
50
|
+
}
|
|
51
|
+
/** Build the Google OAuth authorization URL with the given state parameter. */
|
|
52
|
+
getAuthUrl(state) {
|
|
53
|
+
const params = new URLSearchParams({
|
|
54
|
+
client_id: this.options.clientId,
|
|
55
|
+
redirect_uri: this.options.callbackUrl,
|
|
56
|
+
response_type: "code",
|
|
57
|
+
scope: "openid email profile",
|
|
58
|
+
state,
|
|
59
|
+
access_type: "online"
|
|
60
|
+
});
|
|
61
|
+
return `${GOOGLE_AUTH_URL}?${params.toString()}`;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Exchange an authorization code for a GoogleProfile.
|
|
65
|
+
* Verifies the id_token signature against Google's JWK keyset per
|
|
66
|
+
* https://developers.google.com/identity/openid-connect/openid-connect#validatinganidtoken
|
|
67
|
+
*/
|
|
68
|
+
async exchangeCode(code) {
|
|
69
|
+
const body = new URLSearchParams({
|
|
70
|
+
code,
|
|
71
|
+
client_id: this.options.clientId,
|
|
72
|
+
client_secret: this.options.clientSecret,
|
|
73
|
+
redirect_uri: this.options.callbackUrl,
|
|
74
|
+
grant_type: "authorization_code"
|
|
75
|
+
});
|
|
76
|
+
const tokenRes = await fetch(GOOGLE_TOKEN_URL, {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
79
|
+
body: body.toString()
|
|
80
|
+
});
|
|
81
|
+
if (!tokenRes.ok) {
|
|
82
|
+
const text = await tokenRes.text().catch(() => "");
|
|
83
|
+
throw Object.assign(new Error(`Google token exchange failed: ${text}`), { status: 502 });
|
|
84
|
+
}
|
|
85
|
+
const tokenData = await tokenRes.json();
|
|
86
|
+
if (!tokenData.id_token) {
|
|
87
|
+
throw Object.assign(new Error("Google did not return an id_token"), { status: 502 });
|
|
88
|
+
}
|
|
89
|
+
let payload;
|
|
90
|
+
try {
|
|
91
|
+
const { payload: verified } = await (0, import_jose.jwtVerify)(tokenData.id_token, this.jwks, {
|
|
92
|
+
audience: this.options.clientId,
|
|
93
|
+
issuer: GOOGLE_ISSUERS
|
|
94
|
+
});
|
|
95
|
+
payload = verified;
|
|
96
|
+
} catch (err) {
|
|
97
|
+
throw Object.assign(
|
|
98
|
+
new Error(`id_token verification failed: ${err.message ?? "invalid token"}`),
|
|
99
|
+
{ status: 502 }
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
if (!payload.email_verified) {
|
|
103
|
+
throw Object.assign(new Error("Google email address is not verified"), { status: 403 });
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
googleId: payload.sub,
|
|
107
|
+
email: payload.email,
|
|
108
|
+
firstName: payload.given_name ?? null,
|
|
109
|
+
lastName: payload.family_name ?? null,
|
|
110
|
+
picture: payload.picture ?? null
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
get frontendUrl() {
|
|
114
|
+
return this.options.frontendUrl;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// src/index.ts
|
|
119
|
+
var GoogleOAuthModule = {
|
|
120
|
+
key: "googleOAuthService",
|
|
121
|
+
service: GoogleOAuthService
|
|
122
|
+
};
|
|
123
|
+
var index_default = GoogleOAuthModule;
|
|
124
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
125
|
+
0 && (module.exports = {
|
|
126
|
+
GoogleOAuthService
|
|
127
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// src/service.ts
|
|
2
|
+
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
3
|
+
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
4
|
+
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
5
|
+
var GOOGLE_JWKS_URL = "https://www.googleapis.com/oauth2/v3/certs";
|
|
6
|
+
var GOOGLE_ISSUERS = ["https://accounts.google.com", "accounts.google.com"];
|
|
7
|
+
var GoogleOAuthService = class {
|
|
8
|
+
options;
|
|
9
|
+
jwks;
|
|
10
|
+
constructor(container) {
|
|
11
|
+
this.options = container.resolve("moduleOptions");
|
|
12
|
+
try {
|
|
13
|
+
const parsed = new URL(this.options.frontendUrl);
|
|
14
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
15
|
+
throw new Error("must be http or https");
|
|
16
|
+
}
|
|
17
|
+
} catch {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`[google-oauth] Invalid frontendUrl: "${this.options.frontendUrl}". Must be a valid http:// or https:// URL.`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
this.jwks = createRemoteJWKSet(new URL(GOOGLE_JWKS_URL));
|
|
23
|
+
}
|
|
24
|
+
/** Build the Google OAuth authorization URL with the given state parameter. */
|
|
25
|
+
getAuthUrl(state) {
|
|
26
|
+
const params = new URLSearchParams({
|
|
27
|
+
client_id: this.options.clientId,
|
|
28
|
+
redirect_uri: this.options.callbackUrl,
|
|
29
|
+
response_type: "code",
|
|
30
|
+
scope: "openid email profile",
|
|
31
|
+
state,
|
|
32
|
+
access_type: "online"
|
|
33
|
+
});
|
|
34
|
+
return `${GOOGLE_AUTH_URL}?${params.toString()}`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Exchange an authorization code for a GoogleProfile.
|
|
38
|
+
* Verifies the id_token signature against Google's JWK keyset per
|
|
39
|
+
* https://developers.google.com/identity/openid-connect/openid-connect#validatinganidtoken
|
|
40
|
+
*/
|
|
41
|
+
async exchangeCode(code) {
|
|
42
|
+
const body = new URLSearchParams({
|
|
43
|
+
code,
|
|
44
|
+
client_id: this.options.clientId,
|
|
45
|
+
client_secret: this.options.clientSecret,
|
|
46
|
+
redirect_uri: this.options.callbackUrl,
|
|
47
|
+
grant_type: "authorization_code"
|
|
48
|
+
});
|
|
49
|
+
const tokenRes = await fetch(GOOGLE_TOKEN_URL, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
52
|
+
body: body.toString()
|
|
53
|
+
});
|
|
54
|
+
if (!tokenRes.ok) {
|
|
55
|
+
const text = await tokenRes.text().catch(() => "");
|
|
56
|
+
throw Object.assign(new Error(`Google token exchange failed: ${text}`), { status: 502 });
|
|
57
|
+
}
|
|
58
|
+
const tokenData = await tokenRes.json();
|
|
59
|
+
if (!tokenData.id_token) {
|
|
60
|
+
throw Object.assign(new Error("Google did not return an id_token"), { status: 502 });
|
|
61
|
+
}
|
|
62
|
+
let payload;
|
|
63
|
+
try {
|
|
64
|
+
const { payload: verified } = await jwtVerify(tokenData.id_token, this.jwks, {
|
|
65
|
+
audience: this.options.clientId,
|
|
66
|
+
issuer: GOOGLE_ISSUERS
|
|
67
|
+
});
|
|
68
|
+
payload = verified;
|
|
69
|
+
} catch (err) {
|
|
70
|
+
throw Object.assign(
|
|
71
|
+
new Error(`id_token verification failed: ${err.message ?? "invalid token"}`),
|
|
72
|
+
{ status: 502 }
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (!payload.email_verified) {
|
|
76
|
+
throw Object.assign(new Error("Google email address is not verified"), { status: 403 });
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
googleId: payload.sub,
|
|
80
|
+
email: payload.email,
|
|
81
|
+
firstName: payload.given_name ?? null,
|
|
82
|
+
lastName: payload.family_name ?? null,
|
|
83
|
+
picture: payload.picture ?? null
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
get frontendUrl() {
|
|
87
|
+
return this.options.frontendUrl;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// src/index.ts
|
|
92
|
+
var GoogleOAuthModule = {
|
|
93
|
+
key: "googleOAuthService",
|
|
94
|
+
service: GoogleOAuthService
|
|
95
|
+
};
|
|
96
|
+
var index_default = GoogleOAuthModule;
|
|
97
|
+
export {
|
|
98
|
+
GoogleOAuthService,
|
|
99
|
+
index_default as default
|
|
100
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@meridianjs/google-oauth",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Google OAuth 2.0 sign-in module for Meridian",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.mts",
|
|
12
|
+
"default": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
22
|
+
"dev": "tsup src/index.ts --format esm,cjs --dts --watch",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"clean": "rm -rf dist",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@meridianjs/types": "^0.1.0",
|
|
29
|
+
"jose": "^5.9.6"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"tsup": "^8.3.5",
|
|
33
|
+
"typescript": "*"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist"
|
|
37
|
+
],
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
}
|
|
41
|
+
}
|