Skip to content
Open
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
37 changes: 34 additions & 3 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,34 @@
.github
example
target
# Dependencies
node_modules
npm-debug.log
yarn-debug.log
yarn-error.log

# Environment
.env
.env.local
.env.*.local

# IDE
.idea
.vscode
*.swp
*.swo

# Git
.git
.gitignore

# Docker
Dockerfile
docker-compose.yml
.dockerignore

# Documentation
README.md
LICENSE

# Build output
dist
build
*.log
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
**/target
/static
**/wrangler.toml
**/node_module
**/node_modules
**/dist
57 changes: 31 additions & 26 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,19 +1,6 @@
FROM clux/muslrust:stable as chef
WORKDIR /siwe-oidc
RUN cargo install cargo-chef

FROM chef as dep_planner
COPY ./src/ ./src/
COPY ./Cargo.lock ./
COPY ./Cargo.toml ./
COPY ./siwe-oidc.toml ./
RUN cargo chef prepare --recipe-path recipe.json

FROM chef as dep_cacher
COPY --from=dep_planner /siwe-oidc/recipe.json recipe.json
RUN cargo chef cook --release --recipe-path recipe.json
FROM node:20-alpine as builder

FROM node:18.20.0-alpine3.18 as node_builder
WORKDIR /siwe-oidc

# Reference https://github.com/mhart/alpine-node/issues/27#issuecomment-880663905
RUN apk add --no-cache --virtual .build-deps alpine-sdk python3
Expand All @@ -24,27 +11,45 @@ ARG WALLET_CONNECT_ID
ENV INFURA_ID=${INFURA_ID}
ENV WALLET_CONNECT_ID=${WALLET_CONNECT_ID}

# Copy static files and UI
ADD --chown=node:node ./static /siwe-oidc/static
ADD --chown=node:node ./js/ui /siwe-oidc/js/ui
WORKDIR /siwe-oidc/js/ui
RUN yarn
RUN yarn build

FROM chef as builder
COPY --from=dep_cacher /siwe-oidc/target/ ./target/
COPY --from=dep_cacher $CARGO_HOME $CARGO_HOME
COPY --from=dep_planner /siwe-oidc/ ./
RUN cargo build --release
# Build the Node.js application
FROM node:20-alpine

FROM alpine
COPY --from=builder /siwe-oidc/target/x86_64-unknown-linux-musl/release/siwe-oidc /usr/local/bin/
WORKDIR /siwe-oidc
RUN mkdir -p ./static
COPY --from=node_builder /siwe-oidc/static/ ./static/
COPY --from=builder /siwe-oidc/siwe-oidc.toml ./
COPY ./siwe-oidc-js ./siwe-oidc-js
WORKDIR /siwe-oidc/siwe-oidc-js

# Install dependencies
RUN npm install

# Copy application source
COPY . .

# Copy static files from builder
COPY --from=builder /siwe-oidc/static/ ./static/

# Generate RSA key if not provided
RUN if [ -z "$RSA_PEM" ]; then \
apk add --no-cache openssl && \
openssl genrsa -out /tmp/private.pem 2048 && \
export RSA_PEM=$(cat /tmp/private.pem) && \
rm /tmp/private.pem; \
fi

# Set environment variables
ENV SIWEOIDC_ADDRESS="0.0.0.0"
# Expose port
EXPOSE 8000
ENTRYPOINT ["siwe-oidc"]
# Start the application
CMD ["npm", "start"]

# Labels
LABEL org.opencontainers.image.source https://github.com/spruceid/siwe-oidc
LABEL org.opencontainers.image.description "OpenID Connect Identity Provider for Sign-In with Ethereum"
LABEL org.opencontainers.image.licenses "MIT OR Apache-2.0"
9 changes: 8 additions & 1 deletion js/ui/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,17 @@ import { WagmiProvider, createConfig, fallback, http } from "wagmi";
import { getDefaultConfig } from "connectkit";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { mainnet } from "wagmi/chains";
import { coinbaseWallet } from "wagmi/connectors";

import App from "./App";
import "./index.css";

const coinbaseWalletConnector = coinbaseWallet({
appName: "SIWE | Devfolio",
darkMode: true,
preference: "all",
});

const config = createConfig(
getDefaultConfig({
walletConnectProjectId: process.env.WALLET_CONNECT_ID ?? "",
Expand All @@ -20,7 +27,7 @@ const config = createConfig(
http(), // public fallback
]),
},

connectors: [coinbaseWalletConnector],
// Required
appName: "SIWE | Devfolio",
appUrl: "https://devfolio.co", // your app's url
Expand Down
31 changes: 31 additions & 0 deletions siwe-oidc-js/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "siwe-oidc-js",
"version": "1.0.0",
"description": "OpenID Connect provider with Sign-In with Ethereum support",
"main": "src/index.js",
"type": "module",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js"
},
"dependencies": {
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"ethers": "^6.9.0",
"express": "^4.18.2",
"express-rate-limit": "^7.1.5",
"express-validator": "^7.0.1",
"helmet": "^7.1.0",
"jsonwebtoken": "^9.0.2",
"jwks-rsa": "^3.1.0",
"node-rsa": "^1.1.1",
"redis": "^4.6.11",
"siwe": "^3.0.0",
"viem": "2.28.1",
"wagmi": "^2.15.1"
},
"devDependencies": {
"nodemon": "^3.0.2"
}
}
38 changes: 38 additions & 0 deletions siwe-oidc-js/src/config/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import dotenv from 'dotenv';
import { URL } from 'url';

dotenv.config();

const config = {
address: process.env.ADDRESS || '127.0.0.1',
port: parseInt(process.env.PORT || '8000', 10),
baseUrl: new URL(process.env.SIWEOIDC_BASE_URL || 'http://127.0.0.1:8000'),
rsaPem: process.env.RSA_PEM,
redisUrl: new URL(process.env.SIWEOIDC_REDIS_URL || 'redis://localhost'),
defaultClients: {},
requireSecret: process.env.REQUIRE_SECRET === 'true',
ethProvider: process.env.ETH_PROVIDER ? new URL(process.env.ETH_PROVIDER) : null,

// OIDC Configuration
signingAlg: 'RS256',
kid: 'key1',
scopes: ['openid', 'profile'],
responseTypes: ['code', 'id_token', 'token id_token'],
subjectIdentifierTypes: ['pairwise'],
tokenEndpointAuthMethods: ['client_secret_basic', 'client_secret_post', 'private_key_jwt'],

// Paths
metadataPath: '/.well-known/openid-configuration',
jwkPath: '/jwk',
tokenPath: '/token',
authorizePath: '/authorize',
registerPath: '/register',
clientPath: '/client',
userinfoPath: '/userinfo',
signinPath: '/sign_in',
siweCookieKey: 'siwe',
touPath: '/legal/terms-of-use.pdf',
ppPath: '/legal/privacy-policy.pdf'
};

export default config;
81 changes: 81 additions & 0 deletions siwe-oidc-js/src/db/redis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { createClient } from 'redis';
import config from '../config/index.js';

class RedisClient {
constructor() {
this.client = createClient({
url: config.redisUrl.toString(),
database: 2
});

this.client.on('error', (err) => console.error('Redis Client Error', err));
}

async connect() {
await this.client.connect();
}

async disconnect() {
await this.client.disconnect();
}

// Client management
async getClient(clientId) {
return await this.client.get(`client:${clientId}`);
}

async setClient(clientId, clientData) {
await this.client.set(`client:${clientId}`, JSON.stringify(clientData));
}

async deleteClient(clientId) {
await this.client.del(`client:${clientId}`);
}

// Session management
async getSession(sessionId) {
return await this.client.get(`session:${sessionId}`);
}

async setSession(sessionId, sessionData, ttl = 3600) {
await this.client.set(`session:${sessionId}`, JSON.stringify(sessionData), {
EX: ttl
});
}

async deleteSession(sessionId) {
await this.client.del(`session:${sessionId}`);
}

// Authorization code management
async getAuthCode(code) {
return await this.client.get(`auth_code:${code}`);
}

async setAuthCode(code, codeData, ttl = 300) {
await this.client.set(`auth_code:${code}`, JSON.stringify(codeData), {
EX: ttl
});
}

async deleteAuthCode(code) {
await this.client.del(`auth_code:${code}`);
}

// Token management
async getToken(token) {
return await this.client.get(`token:${token}`);
}

async setToken(token, tokenData, ttl = 3600) {
await this.client.set(`token:${token}`, JSON.stringify(tokenData), {
EX: ttl
});
}

async deleteToken(token) {
await this.client.del(`token:${token}`);
}
}

export default new RedisClient();
67 changes: 67 additions & 0 deletions siwe-oidc-js/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import cookieParser from 'cookie-parser';
import rateLimit from 'express-rate-limit';
import config from './config/index.js';
import db from './db/redis.js';
import oidcRouter from './routes/oidc.js';

const app = express();

// Middleware
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
connectSrc: ["'self'", "wss://relay.walletconnect.org", "https://relay.walletconnect.org"],
}
}
}));

app.use(cors({
origin: true,
credentials: true
}));
app.use(cookieParser());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use(limiter);

// Routes
app.use('/', oidcRouter);

// Error handling
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Internal Server Error' });
});

// Start server
const startServer = async () => {
try {
await db.connect();

app.listen(config.port, () => {
console.log(`Server running at http://${config.address}:${config.port}`);
});
} catch (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
};

// Handle graceful shutdown
process.on('SIGTERM', async () => {
console.log('SIGTERM received. Shutting down gracefully...');
await db.disconnect();
process.exit(0);
});

startServer();
Loading